mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
878 lines
29 KiB
TypeScript
878 lines
29 KiB
TypeScript
// Copyright 2026 OpenObserve Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
import useStreams from "@/composables/useStreams";
|
|
import StreamService from "@/services/stream";
|
|
import { toast } from "@/lib/feedback/Toast/useToast";
|
|
import i18nInstance from "@/locales";
|
|
const t = (i18nInstance.global as any).t;
|
|
|
|
// Mock Stream Service
|
|
vi.mock("@/services/stream", () => ({
|
|
default: {
|
|
nameList: vi.fn(),
|
|
schema: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
// Mock Toast
|
|
const mockToastDismiss = vi.fn();
|
|
|
|
vi.mock("@/lib/feedback/Toast/useToast", () => ({
|
|
toast: vi.fn(),
|
|
}));
|
|
|
|
// Mock utilities
|
|
vi.mock("@/utils/zincutils", () => ({
|
|
deepCopy: vi.fn((obj) => JSON.parse(JSON.stringify(obj))),
|
|
}));
|
|
|
|
// Mock Store
|
|
const createMockStore = () => ({
|
|
state: {
|
|
selectedOrganization: {
|
|
identifier: "test-org",
|
|
},
|
|
streams: {
|
|
logs: null,
|
|
metrics: null,
|
|
traces: null,
|
|
enrichment_tables: null,
|
|
index: null,
|
|
metadata: null,
|
|
streamsIndexMapping: {
|
|
logs: {},
|
|
metrics: {},
|
|
traces: {},
|
|
enrichment_tables: {},
|
|
index: {},
|
|
metadata: {},
|
|
},
|
|
areStreamsFetched: false,
|
|
},
|
|
organizationData: {
|
|
isDataIngested: false,
|
|
},
|
|
},
|
|
dispatch: vi.fn(),
|
|
commit: vi.fn(),
|
|
});
|
|
|
|
let mockStore: ReturnType<typeof createMockStore>;
|
|
|
|
vi.mock("vuex", () => ({
|
|
useStore: () => mockStore,
|
|
}));
|
|
|
|
describe("useStreams Composable", () => {
|
|
let streamsInstance: ReturnType<typeof useStreams>;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(toast).mockReturnValue(mockToastDismiss);
|
|
mockStore = createMockStore();
|
|
|
|
// Set up default mocks
|
|
const mockStreamService = vi.mocked(StreamService);
|
|
mockStreamService.nameList.mockResolvedValue({
|
|
data: {
|
|
list: [{ name: "test-stream", stream_type: "logs" }],
|
|
},
|
|
});
|
|
mockStreamService.schema.mockResolvedValue({
|
|
data: {
|
|
name: "test-stream",
|
|
stream_type: "logs",
|
|
schema: [
|
|
{ name: "field1", type: "string" },
|
|
{ name: "_o2_id", type: "string" },
|
|
{ name: "_original", type: "string" },
|
|
{ name: "_all_values", type: "string" },
|
|
],
|
|
},
|
|
});
|
|
|
|
streamsInstance = useStreams(t);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllTimers();
|
|
});
|
|
|
|
// Test 1: Composable Initialization
|
|
describe("Composable Initialization", () => {
|
|
it("should initialize useStreams composable with all methods", () => {
|
|
expect(streamsInstance).toBeDefined();
|
|
|
|
const expectedMethods = [
|
|
"getStreams",
|
|
"getStream",
|
|
"setStreams",
|
|
"getMultiStreams",
|
|
"resetStreams",
|
|
"removeStream",
|
|
"addStream",
|
|
"getUpdatedSettings",
|
|
"resetStreamType",
|
|
"getPaginatedStreams",
|
|
"isStreamExists",
|
|
"isStreamFetched",
|
|
"addNewStreams",
|
|
"updateStreamsInStore",
|
|
"updateStreamIndexMappingInStore",
|
|
"updateStreamsFetchedInStore",
|
|
"getAllStreamsPayload",
|
|
"removeSchemaFields",
|
|
"getStreamPayload",
|
|
"compareArrays",
|
|
"deepEqual",
|
|
"comparePatternAssociations",
|
|
];
|
|
|
|
expectedMethods.forEach((method) => {
|
|
expect(streamsInstance).toHaveProperty(method);
|
|
expect(typeof streamsInstance[method]).toBe("function");
|
|
});
|
|
});
|
|
});
|
|
|
|
// Test 2-5: Store Update Functions
|
|
describe("Store Update Functions", () => {
|
|
it("should call store dispatch for updateStreamsInStore", () => {
|
|
const streamType = "logs";
|
|
const streams = { list: [] };
|
|
|
|
streamsInstance.updateStreamsInStore(streamType, streams);
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith("streams/setStreams", {
|
|
streamType,
|
|
streams,
|
|
});
|
|
});
|
|
|
|
it("should call store commit for updateStreamIndexMappingInStore", () => {
|
|
const indexMapping = { logs: { test: 0 } };
|
|
|
|
streamsInstance.updateStreamIndexMappingInStore(indexMapping);
|
|
|
|
expect(mockStore.commit).toHaveBeenCalledWith(
|
|
"streams/updateStreamIndexMapping",
|
|
indexMapping,
|
|
);
|
|
});
|
|
|
|
it("should call store commit for updateStreamsFetchedInStore", () => {
|
|
const areStreamsFetched = true;
|
|
|
|
streamsInstance.updateStreamsFetchedInStore(areStreamsFetched);
|
|
|
|
expect(mockStore.commit).toHaveBeenCalledWith(
|
|
"streams/updateStreamsFetched",
|
|
areStreamsFetched,
|
|
);
|
|
});
|
|
|
|
it("should return stream payload structure from getStreamPayload", () => {
|
|
const payload = streamsInstance.getStreamPayload();
|
|
|
|
expect(payload).toEqual({
|
|
name: "",
|
|
list: [],
|
|
schema: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
// Test 6-10: Stream Fetching Functions
|
|
describe("Stream Fetching Functions", () => {
|
|
it("should get streams for specific stream type", async () => {
|
|
mockStore.state.streams.logs = null; // Not fetched yet
|
|
|
|
const result = await streamsInstance.getStreams("logs", false, false);
|
|
|
|
expect(StreamService.nameList).toHaveBeenCalledWith("test-org", "logs", false);
|
|
expect(result).toHaveProperty("name", "logs");
|
|
expect(result).toHaveProperty("list");
|
|
});
|
|
|
|
it("should get all streams when streamName is 'all'", async () => {
|
|
mockStore.state.streams.areStreamsFetched = false;
|
|
|
|
const result = await streamsInstance.getStreams("all", false, false);
|
|
|
|
expect(result).toHaveProperty("name", "all");
|
|
});
|
|
|
|
it("should return cached streams if already fetched", async () => {
|
|
// Mock streams as already fetched
|
|
mockStore.state.streams.logs = { list: [{ name: "cached" }] };
|
|
mockStore.state.streams.areStreamsFetched = false;
|
|
|
|
const result = await streamsInstance.getStreams("logs", false, false);
|
|
|
|
expect(result).toEqual({ list: [{ name: "cached" }] });
|
|
expect(StreamService.nameList).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should force fetch streams when force parameter is true", async () => {
|
|
mockStore.state.streams.logs = { list: [{ name: "cached" }] };
|
|
|
|
await streamsInstance.getStreams("logs", false, false, true);
|
|
|
|
expect(StreamService.nameList).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle getPaginatedStreams with all parameters", async () => {
|
|
const result = await streamsInstance.getPaginatedStreams(
|
|
"logs",
|
|
false,
|
|
true,
|
|
0,
|
|
50,
|
|
"test",
|
|
"name",
|
|
true,
|
|
);
|
|
|
|
expect(StreamService.nameList).toHaveBeenCalledWith(
|
|
"test-org",
|
|
"logs",
|
|
false,
|
|
0,
|
|
50,
|
|
"test",
|
|
"name",
|
|
true,
|
|
);
|
|
expect(result).toHaveProperty("name", "logs");
|
|
expect(result).toHaveProperty("total");
|
|
});
|
|
});
|
|
|
|
// Test 11-15: Individual Stream Functions
|
|
describe("Individual Stream Functions", () => {
|
|
it("should return null for getStream with empty parameters", async () => {
|
|
const result = await streamsInstance.getStream("", "logs", false);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should get stream with schema when requested", async () => {
|
|
// Set up stream in cache
|
|
mockStore.state.streams.logs = {
|
|
list: [{ name: "test-stream", schema: [] }],
|
|
};
|
|
mockStore.state.streams.streamsIndexMapping.logs["test-stream"] = 0;
|
|
|
|
await streamsInstance.getStream("test-stream", "logs", true);
|
|
|
|
expect(StreamService.schema).toHaveBeenCalledWith("test-org", "test-stream", "logs");
|
|
});
|
|
|
|
it("should reject with 'Stream Not Found' for non-existent stream", async () => {
|
|
mockStore.state.streams.logs = { list: [] };
|
|
|
|
await expect(streamsInstance.getStream("non-existent", "logs", false)).rejects.toThrow(
|
|
"Stream 'non-existent' not found for type 'logs'",
|
|
);
|
|
});
|
|
|
|
it("should handle getMultiStreams with valid streams", async () => {
|
|
const streams = [
|
|
{ streamName: "stream1", streamType: "logs", schema: false },
|
|
{ streamName: "stream2", streamType: "metrics", schema: true },
|
|
];
|
|
|
|
mockStore.state.streams.logs = { list: [{ name: "stream1" }] };
|
|
mockStore.state.streams.metrics = { list: [{ name: "stream2" }] };
|
|
mockStore.state.streams.streamsIndexMapping.logs["stream1"] = 0;
|
|
mockStore.state.streams.streamsIndexMapping.metrics["stream2"] = 0;
|
|
|
|
const results = await streamsInstance.getMultiStreams(streams);
|
|
|
|
expect(results).toHaveLength(2);
|
|
});
|
|
|
|
it("should return null for getMultiStreams with empty parameters", async () => {
|
|
const streams = [
|
|
{ streamName: "", streamType: "logs", schema: false },
|
|
{ streamName: "test", streamType: "", schema: false },
|
|
];
|
|
|
|
const results = await streamsInstance.getMultiStreams(streams);
|
|
|
|
expect(results).toEqual([null, null]);
|
|
});
|
|
});
|
|
|
|
// Test 16-20: Schema and Data Manipulation
|
|
describe("Schema and Data Manipulation", () => {
|
|
it("should remove schema fields (_o2_id, _original, _all_values)", () => {
|
|
const streamData = {
|
|
name: "test",
|
|
schema: [
|
|
{ name: "field1", type: "string" },
|
|
{ name: "_o2_id", type: "string" },
|
|
{ name: "_original", type: "string" },
|
|
{ name: "_all_values", type: "string" },
|
|
{ name: "field2", type: "number" },
|
|
],
|
|
};
|
|
|
|
const result = streamsInstance.removeSchemaFields(streamData);
|
|
|
|
expect(result.schema).toHaveLength(2);
|
|
expect(result.schema.map((f: any) => f.name)).toEqual(["field1", "field2"]);
|
|
});
|
|
|
|
it("should handle removeSchemaFields with no schema", () => {
|
|
const streamData = { name: "test" };
|
|
|
|
const result = streamsInstance.removeSchemaFields(streamData);
|
|
|
|
expect(result).toEqual({ name: "test" });
|
|
});
|
|
|
|
it("should check if stream is fetched correctly", () => {
|
|
// Test with specific stream type
|
|
mockStore.state.streams.logs = { list: [{ name: "test" }] };
|
|
expect(streamsInstance.isStreamFetched("logs")).toBe(true);
|
|
|
|
mockStore.state.streams.metrics = null;
|
|
expect(streamsInstance.isStreamFetched("metrics")).toBe(false);
|
|
|
|
// Test with 'all'
|
|
mockStore.state.streams.areAllStreamsFetched = true;
|
|
expect(streamsInstance.isStreamFetched("all")).toBe(true);
|
|
});
|
|
|
|
it("should check if specific stream exists", () => {
|
|
mockStore.state.streams.streamsIndexMapping.logs["existing-stream"] = 0;
|
|
|
|
expect(streamsInstance.isStreamExists("existing-stream", "logs")).toBe(true);
|
|
expect(streamsInstance.isStreamExists("non-existing", "logs")).toBe(false);
|
|
});
|
|
|
|
it("should handle isStreamExists with missing stream type", () => {
|
|
mockStore.state.streams.streamsIndexMapping = {};
|
|
|
|
expect(streamsInstance.isStreamExists("any-stream", "logs")).toBe(false);
|
|
});
|
|
});
|
|
|
|
// Test 21-25: Stream Management Functions
|
|
describe("Stream Management Functions", () => {
|
|
it("should set streams and update store correctly", () => {
|
|
const streamList = [
|
|
{ name: "stream1", stream_type: "logs" },
|
|
{ name: "stream2", stream_type: "logs" },
|
|
];
|
|
|
|
streamsInstance.setStreams("logs", streamList);
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "logs",
|
|
streams: expect.objectContaining({
|
|
name: "logs",
|
|
list: streamList,
|
|
schema: false,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should handle setStreams with 'all' streamName", () => {
|
|
const streamList = [
|
|
{ name: "stream1", stream_type: "logs" },
|
|
{ name: "stream2", stream_type: "metrics" },
|
|
];
|
|
|
|
streamsInstance.setStreams("all", streamList);
|
|
|
|
// Should dispatch multiple times for different stream types
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "logs",
|
|
}),
|
|
);
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "metrics",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should add stream to existing cache", async () => {
|
|
const newStream = { name: "new-stream", stream_type: "logs" };
|
|
mockStore.state.streams.logs = { list: [] };
|
|
mockStore.state.streams.streamsIndexMapping.logs = {};
|
|
|
|
await streamsInstance.addStream(newStream);
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "logs",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("should not add duplicate stream", async () => {
|
|
const existingStream = { name: "existing", stream_type: "logs" };
|
|
mockStore.state.streams.logs = { list: [] };
|
|
mockStore.state.streams.streamsIndexMapping.logs = { existing: 0 };
|
|
|
|
await streamsInstance.addStream(existingStream);
|
|
|
|
// Should not call dispatch since stream already exists
|
|
expect(mockStore.dispatch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should remove stream from cache correctly", () => {
|
|
mockStore.state.streams.logs = {
|
|
list: [{ name: "stream1" }, { name: "stream2" }, { name: "stream3" }],
|
|
};
|
|
mockStore.state.streams.streamsIndexMapping.logs = {
|
|
stream1: 0,
|
|
stream2: 1,
|
|
stream3: 2,
|
|
};
|
|
|
|
streamsInstance.removeStream("stream2", "logs");
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
// Test 26-30: Utility Functions
|
|
describe("Utility Functions", () => {
|
|
it("should compare arrays correctly", () => {
|
|
const previousArray = {
|
|
0: { field: "field1", value: "old" },
|
|
1: { field: "field2", value: "same" },
|
|
};
|
|
const currentArray = [
|
|
{ field: "field2", value: "same" },
|
|
{ field: "field3", value: "new" },
|
|
];
|
|
|
|
const result = streamsInstance.compareArrays(previousArray, currentArray);
|
|
|
|
expect(result.add).toHaveLength(1);
|
|
expect(result.add[0].field).toBe("field3");
|
|
expect(result.remove).toHaveLength(1);
|
|
expect(result.remove[0].field).toBe("field1");
|
|
});
|
|
|
|
it("should perform deep equality check correctly", () => {
|
|
const obj1 = { a: 1, b: { c: 2 } };
|
|
const obj2 = { a: 1, b: { c: 2 } };
|
|
const obj3 = { a: 1, b: { c: 3 } };
|
|
|
|
expect(streamsInstance.deepEqual(obj1, obj2)).toBe(true);
|
|
expect(streamsInstance.deepEqual(obj1, obj3)).toBe(false);
|
|
expect(streamsInstance.deepEqual(null, null)).toBe(true);
|
|
expect(streamsInstance.deepEqual(null, obj1)).toBe(false);
|
|
});
|
|
|
|
it("should handle deepEqual with different key lengths", () => {
|
|
const obj1 = { a: 1, b: 2 };
|
|
const obj2 = { a: 1, b: 2, c: 3 };
|
|
|
|
expect(streamsInstance.deepEqual(obj1, obj2)).toBe(false);
|
|
});
|
|
|
|
it("should compare pattern associations correctly", () => {
|
|
const prev = [
|
|
{ field: "field1", pattern_id: "p1", policy: "policy1", apply_at: "ingest" },
|
|
{ field: "field2", pattern_id: "p2", policy: "policy2", apply_at: null },
|
|
];
|
|
const curr = [
|
|
{ field: "field1", pattern_id: "p1", policy: "policy1", apply_at: "ingest" },
|
|
{ field: "field3", pattern_id: "p3", policy: "policy3", apply_at: "query" },
|
|
];
|
|
|
|
const result = streamsInstance.comparePatternAssociations(prev, curr);
|
|
|
|
expect(result.add).toHaveLength(1);
|
|
expect(result.add[0].field).toBe("field3");
|
|
expect(result.remove).toHaveLength(1);
|
|
expect(result.remove[0].field).toBe("field2");
|
|
});
|
|
|
|
it("should handle pattern associations with null apply_at", () => {
|
|
const prev = [{ field: "field1", pattern_id: "p1", policy: "policy1", apply_at: null }];
|
|
const curr = [{ field: "field1", pattern_id: "p1", policy: "policy1", apply_at: undefined }];
|
|
|
|
const result = streamsInstance.comparePatternAssociations(prev, curr);
|
|
|
|
expect(result.add).toHaveLength(0);
|
|
expect(result.remove).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// Test 31-35: Settings and Configuration
|
|
describe("Settings and Configuration Functions", () => {
|
|
it("should get updated settings for various attributes", () => {
|
|
const previousSettings = {
|
|
fields: ["field1", "field2"],
|
|
partition_keys: [{ field: "pk1", disabled: false }],
|
|
index_fields: ["index1"],
|
|
full_text_search_keys: ["fts1"],
|
|
bloom_filter_fields: ["bloom1"],
|
|
defined_schema_fields: ["schema1"],
|
|
extended_retention_days: [{ days: 30 }],
|
|
pattern_associations: [
|
|
{ field: "f1", pattern_id: "p1", policy: "pol1", apply_at: "ingest" },
|
|
],
|
|
};
|
|
|
|
const currentSettings = {
|
|
fields: ["field1", "field3"],
|
|
partition_keys: [{ field: "pk2", disabled: false }],
|
|
index_fields: ["index1", "index2"],
|
|
full_text_search_keys: ["fts2"],
|
|
bloom_filter_fields: ["bloom2"],
|
|
defined_schema_fields: ["schema2"],
|
|
extended_retention_days: [{ days: 60 }],
|
|
pattern_associations: [
|
|
{ field: "f2", pattern_id: "p2", policy: "pol2", apply_at: "query" },
|
|
],
|
|
};
|
|
|
|
const result = streamsInstance.getUpdatedSettings(previousSettings, currentSettings);
|
|
|
|
expect(result.fields.add).toEqual(["field3"]);
|
|
expect(result.fields.remove).toEqual(["field2"]);
|
|
expect(result.index_fields.add).toEqual(["index2"]);
|
|
});
|
|
|
|
it("should handle empty settings in getUpdatedSettings", () => {
|
|
const result = streamsInstance.getUpdatedSettings({}, {});
|
|
|
|
expect(result).toHaveProperty("fields");
|
|
expect(result).toHaveProperty("partition_keys");
|
|
expect(result.fields.add).toEqual([]);
|
|
expect(result.fields.remove).toEqual([]);
|
|
});
|
|
|
|
it("should reset specific stream type", () => {
|
|
streamsInstance.resetStreamType("logs");
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith("streams/setStreams", {
|
|
streamType: "logs",
|
|
streams: {},
|
|
});
|
|
});
|
|
|
|
it("should handle resetStreamType with empty string", () => {
|
|
streamsInstance.resetStreamType("");
|
|
|
|
// Should not call dispatch
|
|
expect(mockStore.dispatch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle resetStreamType error gracefully", () => {
|
|
// Mock store dispatch to throw error
|
|
mockStore.dispatch = vi.fn().mockImplementation(() => {
|
|
throw new Error("Test error");
|
|
});
|
|
|
|
streamsInstance.resetStreamType("logs");
|
|
|
|
// Restore dispatch for subsequent tests
|
|
mockStore.dispatch = vi.fn();
|
|
});
|
|
});
|
|
|
|
// Test 36-40: Advanced Stream Operations
|
|
describe("Advanced Stream Operations", () => {
|
|
it("should reset all streams completely", () => {
|
|
streamsInstance.resetStreams();
|
|
|
|
// Should reset all stream types
|
|
const streamTypes = ["logs", "metrics", "traces", "enrichment_tables", "index", "metadata"];
|
|
streamTypes.forEach((type) => {
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith("streams/setStreams", {
|
|
streamType: type,
|
|
streams: null,
|
|
});
|
|
});
|
|
|
|
expect(mockStore.commit).toHaveBeenCalledWith("streams/updateStreamIndexMapping", {});
|
|
expect(mockStore.commit).toHaveBeenCalledWith("streams/updateStreamsFetched", false);
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith("setIsDataIngested", false);
|
|
});
|
|
|
|
it("should get all streams payload correctly", () => {
|
|
mockStore.state.streams.logs = { list: [{ name: "log1" }] };
|
|
mockStore.state.streams.metrics = { list: [{ name: "metric1" }] };
|
|
|
|
const result = streamsInstance.getAllStreamsPayload();
|
|
|
|
expect(result.name).toBe("all");
|
|
expect(result.schema).toBe(false);
|
|
expect(result.list).toEqual([{ name: "log1" }, { name: "metric1" }]);
|
|
});
|
|
|
|
it("should add new streams only if they don't exist", () => {
|
|
const existingStreams = [{ name: "existing", stream_type: "logs" }];
|
|
const newStreams = [
|
|
{ name: "existing", stream_type: "logs" },
|
|
{ name: "new-stream", stream_type: "logs" },
|
|
];
|
|
|
|
mockStore.state.streams.logs = { list: existingStreams };
|
|
mockStore.state.streams.streamsIndexMapping.logs = { existing: 0 };
|
|
|
|
streamsInstance.addNewStreams("logs", newStreams);
|
|
|
|
// Should only add the new stream
|
|
expect(mockStore.dispatch).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should not add streams if all exist", () => {
|
|
const existingStreams = [{ name: "existing", stream_type: "logs" }];
|
|
const duplicateStreams = [{ name: "existing", stream_type: "logs" }];
|
|
|
|
// Set up the cache properly
|
|
mockStore.state.streams.logs = { list: existingStreams };
|
|
mockStore.state.streams.streamsIndexMapping.logs = { existing: 0 };
|
|
|
|
// Clear previous calls
|
|
vi.clearAllMocks();
|
|
|
|
streamsInstance.addNewStreams("logs", duplicateStreams);
|
|
|
|
// The function will still call setStreams with existing streams + no new streams
|
|
// So we verify it's called with the same streams (no new additions)
|
|
const setStreamsCalls = mockStore.dispatch.mock.calls.filter(
|
|
(call) => call[0] === "streams/setStreams",
|
|
);
|
|
expect(setStreamsCalls).toHaveLength(1);
|
|
expect(setStreamsCalls[0][1].streams.list).toEqual(existingStreams);
|
|
});
|
|
|
|
it("should not seed cache when addNewStreams called with empty cache", () => {
|
|
const newStreams = [{ name: "new-stream", stream_type: "logs" }];
|
|
|
|
mockStore.state.streams.logs = null;
|
|
|
|
streamsInstance.addNewStreams("logs", newStreams);
|
|
|
|
// Should NOT dispatch — partial results must not seed the cache,
|
|
// otherwise a full fetch on the logs page would be skipped.
|
|
expect(mockStore.dispatch).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
// Test 41-45: Error Handling and Edge Cases
|
|
describe("Error Handling and Edge Cases", () => {
|
|
it("should handle StreamService.nameList rejection", async () => {
|
|
vi.mocked(StreamService.nameList).mockRejectedValueOnce(new Error("Service error"));
|
|
|
|
await expect(streamsInstance.getStreams("logs", false, false)).rejects.toThrow(
|
|
"Service error",
|
|
);
|
|
});
|
|
|
|
it("should handle StreamService.schema rejection in getStream", async () => {
|
|
mockStore.state.streams.logs = { list: [{ name: "test", schema: [] }] };
|
|
mockStore.state.streams.streamsIndexMapping.logs["test"] = 0;
|
|
|
|
vi.mocked(StreamService.schema).mockRejectedValueOnce(new Error("Schema error"));
|
|
|
|
await expect(streamsInstance.getStream("test", "logs", true)).rejects.toThrow(
|
|
"Error while fetching schema: Schema error",
|
|
);
|
|
});
|
|
|
|
it("should handle getPaginatedStreams service error", async () => {
|
|
vi.mocked(StreamService.nameList).mockRejectedValueOnce(new Error("Pagination error"));
|
|
|
|
await expect(streamsInstance.getPaginatedStreams("logs", false, true)).rejects.toThrow(
|
|
"Pagination error",
|
|
);
|
|
});
|
|
|
|
it("should handle getMultiStreams with schema fetch error", async () => {
|
|
const streams = [{ streamName: "test", streamType: "logs", schema: true }];
|
|
|
|
mockStore.state.streams.logs = { list: [{ name: "test", schema: null }] };
|
|
mockStore.state.streams.streamsIndexMapping.logs["test"] = 0;
|
|
|
|
vi.mocked(StreamService.schema).mockRejectedValueOnce(new Error("Schema error"));
|
|
|
|
await expect(streamsInstance.getMultiStreams(streams)).rejects.toThrow("Schema error");
|
|
});
|
|
|
|
it("should handle isStreamExists with store access error", () => {
|
|
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
|
|
// Create a store with an invalid structure to trigger the try-catch
|
|
const originalStore = mockStore;
|
|
mockStore.state.streams.streamsIndexMapping = {
|
|
get logs() {
|
|
throw new Error("Access error");
|
|
},
|
|
};
|
|
|
|
const result = streamsInstance.isStreamExists("test", "logs");
|
|
|
|
expect(result).toBe(false);
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
"Error checking if stream exists:",
|
|
expect.any(Error),
|
|
);
|
|
|
|
// Restore
|
|
mockStore = originalStore;
|
|
consoleWarnSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
// Test 46-50: Promise and Async Behavior
|
|
describe("Promise and Async Behavior", () => {
|
|
it("should handle concurrent getStreams calls properly", async () => {
|
|
mockStore.state.streams.logs = null;
|
|
|
|
const promise1 = streamsInstance.getStreams("logs", false, false);
|
|
const promise2 = streamsInstance.getStreams("logs", false, false);
|
|
|
|
const results = await Promise.all([promise1, promise2]);
|
|
|
|
expect(results).toHaveLength(2);
|
|
// Due to the implementation, each composable instance may call the service
|
|
expect(StreamService.nameList).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle notification dismissal in getStreams", async () => {
|
|
mockStore.state.streams.logs = null;
|
|
|
|
await streamsInstance.getStreams("logs", false, true);
|
|
|
|
expect(vi.mocked(toast)).toHaveBeenCalledWith({
|
|
variant: "loading",
|
|
message: "Please wait while loading streams...",
|
|
timeout: 0,
|
|
});
|
|
expect(mockToastDismiss).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle notification dismissal in getPaginatedStreams", async () => {
|
|
await streamsInstance.getPaginatedStreams("logs", false, true);
|
|
|
|
expect(mockToastDismiss).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle getStreams with notify=false", async () => {
|
|
mockStore.state.streams.logs = null;
|
|
|
|
await streamsInstance.getStreams("logs", false, false);
|
|
|
|
expect(vi.mocked(toast)).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle Promise.allSettled with mixed results in getStreams", async () => {
|
|
mockStore.state.streams.areStreamsFetched = false;
|
|
|
|
// Mock one success and one failure
|
|
vi.mocked(StreamService.nameList)
|
|
.mockResolvedValueOnce({ data: { list: [{ name: "success", stream_type: "logs" }] } })
|
|
.mockRejectedValueOnce(new Error("Failed"));
|
|
|
|
const result = await streamsInstance.getStreams("all", false, false);
|
|
|
|
expect(result.name).toBe("all");
|
|
});
|
|
});
|
|
|
|
// Test 51-54: Data Ingestion and Store Integration
|
|
describe("Data Ingestion and Store Integration", () => {
|
|
it("should set isDataIngested when streams are added", () => {
|
|
mockStore.state.organizationData.isDataIngested = false;
|
|
const streamList = [{ name: "test", stream_type: "logs" }];
|
|
|
|
streamsInstance.setStreams("logs", streamList);
|
|
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith("setIsDataIngested", true);
|
|
});
|
|
|
|
it("should not set isDataIngested for empty stream list", () => {
|
|
mockStore.state.organizationData.isDataIngested = false;
|
|
|
|
streamsInstance.setStreams("logs", []);
|
|
|
|
expect(mockStore.dispatch).not.toHaveBeenCalledWith("setIsDataIngested", expect.anything());
|
|
});
|
|
|
|
it("should handle deepEqual with nested objects", () => {
|
|
const obj1 = { a: { b: { c: 1 } } };
|
|
const obj2 = { a: { b: { c: 1 } } };
|
|
const obj3 = { a: { b: { c: 2 } } };
|
|
|
|
expect(streamsInstance.deepEqual(obj1, obj2)).toBe(true);
|
|
expect(streamsInstance.deepEqual(obj1, obj3)).toBe(false);
|
|
});
|
|
|
|
it("should handle setStreams force parameter behavior", () => {
|
|
const streamList = [{ name: "test", stream_type: "logs" }];
|
|
|
|
// Test 1: setStreams with force=true always sets streams
|
|
vi.clearAllMocks();
|
|
streamsInstance.setStreams("logs", streamList, true);
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "logs",
|
|
streams: expect.objectContaining({
|
|
list: streamList,
|
|
name: "logs",
|
|
}),
|
|
}),
|
|
);
|
|
|
|
// Test 2: setStreams with force=true again should still work
|
|
vi.clearAllMocks();
|
|
streamsInstance.setStreams("logs", streamList, true);
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "logs",
|
|
}),
|
|
);
|
|
|
|
// Test 3: Verify that force parameter is passed correctly to the function
|
|
vi.clearAllMocks();
|
|
const emptyStreamList: any[] = [];
|
|
streamsInstance.setStreams("metrics", emptyStreamList, true);
|
|
expect(mockStore.dispatch).toHaveBeenCalledWith(
|
|
"streams/setStreams",
|
|
expect.objectContaining({
|
|
streamType: "metrics",
|
|
streams: expect.objectContaining({
|
|
name: "metrics",
|
|
list: emptyStreamList,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
});
|