mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
### **PR Type** Tests, Enhancement ___ ### **Description** - Add extensive LogStream component tests - Introduce service unit tests coverage - Improve test mocks and handlers - Fix brittle UI tests and stubs ___ ### Diagram Walkthrough ```mermaid flowchart LR LogStreamSpec["LogStream.spec.ts"] -- adds comprehensive component tests --> VueApp["Vue components"] ServiceSpecs["tickets/billings/organizations.spec.ts"] -- mock http and assert endpoints --> HTTP["HTTP layer"] UtilsSpec["convertDataIntoUnitValue.spec.ts"] -- validate formatting and conversions --> DashboardUtils["Dashboard utils"] TestInfra["handlers.ts + stubs"] -- stabilize mocks --> TestRunner["Vitest env"] UIFixes["User/AddUser/Reports specs"] -- adjust stubs and expectations --> UIComponents["UI components"] ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><details><summary>14 files</summary><table> <tr> <td><strong>LogStream.spec.ts</strong><dd><code>Rewrite and greatly expand LogStream tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-5d1ec597d0896fc4768515852054db8c919e048d2e8f6013bfbc4c42fa7503da">+1197/-82</a></td> </tr> <tr> <td><strong>tickets.spec.ts</strong><dd><code>Add full CRUD tests for tickets service</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-6326b5fdbd0493710b156bfb06237d5906fc493de034e872a879b9d59245d483">+565/-0</a> </td> </tr> <tr> <td><strong>billings.spec.ts</strong><dd><code>Add billing workflows and error tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-8152eb5ae97615b253cd61eedea9147bf255cf444eefea25e1a4e954ffc7ee65">+547/-0</a> </td> </tr> <tr> <td><strong>convertDataIntoUnitValue.spec.ts</strong><dd><code>Test data unit conversion and formatting utils</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-e9d8a984ed78af13e4d4424c605a3a55fe6ad2a3f7e403fad156ce17ffdc3bdd">+452/-0</a> </td> </tr> <tr> <td><strong>organizations.spec.ts</strong><dd><code>Cover organizations service endpoints and flows</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-d891fff7a737f937ff3cad0710f07a99479d7160e31928259d1aec4ebfaf367d">+519/-0</a> </td> </tr> <tr> <td><strong>AddAlert.spec.ts</strong><dd><code>Add missing composable/service mocks for alerts</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-84362398fea8abd9acf30f4f40ba8d4cf27348677454017bd4e6f568d5176ac6">+47/-0</a> </td> </tr> <tr> <td><strong>User.spec.js</strong><dd><code>Stabilize user tests and adjust stubs</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-f792c36cfff4d60265030596a1316a3fe207a1bcafc53cd0223a4873f9daf25b">+8/-20</a> </td> </tr> <tr> <td><strong>users.spec.ts</strong><dd><code>Add users service tests (CRUD, roles)</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-1e0367915c28073772fd91455c185fa522f17453fc868207b030f606fb7c0dee">+425/-0</a> </td> </tr> <tr> <td><strong>common.spec.ts</strong><dd><code>Add common utilities unit tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-37bf940c4e06c75c7443e4342a8e2896adbb8cff23fad8f7227e4cd58537e6fb">+395/-0</a> </td> </tr> <tr> <td><strong>useStreams.spec.ts</strong><dd><code>Add tests for useStreams composable pagination</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-513e2d53c46c553904a054e9c2cd12d33e800afc54fe5b4192c9782382ecd79b">+285/-0</a> </td> </tr> <tr> <td><strong>auth.spec.ts</strong><dd><code>Add authentication service tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-ff84b903292a1f76994704e04dc9a7aaf6788497f8b561b7f693f6146a93b140">+199/-0</a> </td> </tr> <tr> <td><strong>AddUser.spec.js</strong><dd><code>Update and fix AddUser tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-9f6b815996dc8497b76604e29e7495042466714655abc8b5010430959007f3d0">+62/-1</a> </td> </tr> <tr> <td><strong>ReportList.spec.js</strong><dd><code>Fix failing report list tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-c3de3f1590ac7b4401cf5570a52f0dcf6471898e4ffc7ccbc19166ba600a181f">+46/-1</a> </td> </tr> <tr> <td><strong>CreateReport.spec.js</strong><dd><code>Fix create report test failures</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-8a0f39751d08a258c4997c5e68edaf167ea01477528beee2295dd9f992310cc6">+61/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>handlers.ts</strong><dd><code>Update MSW handlers for functions and alerts</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8025/files#diff-fa72375a2848d474b311c07cf6aef7d6117b1f4d88f56a74c9207f39df72333c">+13/-3</a> </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
370 lines
12 KiB
TypeScript
370 lines
12 KiB
TypeScript
// Copyright 2023 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 } from "vitest";
|
|
import { getDefaultOrganization, redirectUser, logsErrorMessage } from "@/utils/common";
|
|
import organizationsService from "@/services/organizations";
|
|
import * as zincutils from "@/utils/zincutils";
|
|
|
|
vi.mock("@/services/organizations");
|
|
vi.mock("@/utils/zincutils");
|
|
vi.mock("vuex", () => ({
|
|
useStore: () => ({
|
|
dispatch: vi.fn(),
|
|
state: {}
|
|
})
|
|
}));
|
|
|
|
describe("Common Utils", () => {
|
|
const mockOrganizationsService = vi.mocked(organizationsService);
|
|
const mockZincutils = vi.mocked(zincutils);
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
// Mock window.location
|
|
Object.defineProperty(window, 'location', {
|
|
value: {
|
|
href: '',
|
|
replace: vi.fn()
|
|
},
|
|
writable: true
|
|
});
|
|
});
|
|
|
|
describe("getDefaultOrganization", () => {
|
|
const mockUserInfo = {
|
|
email: "test@example.com",
|
|
name: "Test User"
|
|
};
|
|
|
|
const mockOrgIdentifier = "test-org";
|
|
|
|
const mockOrgsResponse = {
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 1,
|
|
name: "Default Organization",
|
|
org_type: "default",
|
|
identifier: "default-org",
|
|
user_obj: { email: "test@example.com" },
|
|
ingest_threshold: 1000,
|
|
search_threshold: 500,
|
|
note: "Default organization"
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Secondary Organization",
|
|
org_type: "custom",
|
|
identifier: "secondary-org",
|
|
user_obj: { email: "test@example.com" },
|
|
ingest_threshold: 2000,
|
|
search_threshold: 1000,
|
|
note: "Secondary organization"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
it("should fetch and process organization list successfully", async () => {
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(mockOrgsResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
mockZincutils.getPath = vi.fn().mockReturnValue("/");
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalledWith(
|
|
0, 100000, "id", false, "", mockOrgIdentifier
|
|
);
|
|
});
|
|
|
|
it("should select default organization when user matches", async () => {
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(mockOrgsResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockZincutils.useLocalOrganization).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle single organization case", async () => {
|
|
const singleOrgResponse = {
|
|
data: {
|
|
data: [mockOrgsResponse.data.data[0]]
|
|
}
|
|
};
|
|
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(singleOrgResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle local organization mismatch", async () => {
|
|
const localOrg = {
|
|
value: {
|
|
user_email: "different@example.com",
|
|
name: "Different User Org"
|
|
}
|
|
};
|
|
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(mockOrgsResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue(localOrg);
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockZincutils.useLocalOrganization).toHaveBeenCalledWith("");
|
|
});
|
|
|
|
it("should handle API errors", async () => {
|
|
const apiError = new Error("API request failed");
|
|
mockOrganizationsService.os_list = vi.fn().mockRejectedValue(apiError);
|
|
|
|
await expect(getDefaultOrganization(mockUserInfo, mockOrgIdentifier))
|
|
.rejects.toThrow("API request failed");
|
|
});
|
|
|
|
it("should map organization data correctly", async () => {
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(mockOrgsResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle empty organization list", async () => {
|
|
const emptyResponse = {
|
|
data: {
|
|
data: []
|
|
}
|
|
};
|
|
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(emptyResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
await getDefaultOrganization(mockUserInfo, mockOrgIdentifier);
|
|
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("redirectUser", () => {
|
|
beforeEach(() => {
|
|
mockZincutils.getPath = vi.fn().mockReturnValue("/default-path");
|
|
});
|
|
|
|
it("should redirect to external URL when redirectURI contains http", () => {
|
|
const externalUrl = "https://external-site.com/redirect";
|
|
|
|
redirectUser(externalUrl);
|
|
|
|
expect(window.location.href).toBe(externalUrl);
|
|
});
|
|
|
|
it("should use replace for internal redirects", () => {
|
|
const internalPath = "/internal/path";
|
|
const replaceSpy = vi.spyOn(window.location, 'replace');
|
|
|
|
redirectUser(internalPath);
|
|
|
|
expect(replaceSpy).toHaveBeenCalledWith(internalPath);
|
|
});
|
|
|
|
it("should redirect to default path when redirectURI is null", () => {
|
|
const replaceSpy = vi.spyOn(window.location, 'replace');
|
|
|
|
redirectUser(null);
|
|
|
|
expect(replaceSpy).toHaveBeenCalledWith("/default-path");
|
|
expect(mockZincutils.getPath).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should redirect to default path when redirectURI is empty string", () => {
|
|
const replaceSpy = vi.spyOn(window.location, 'replace');
|
|
|
|
redirectUser("");
|
|
|
|
expect(replaceSpy).toHaveBeenCalledWith("/default-path");
|
|
});
|
|
|
|
it("should handle https URLs", () => {
|
|
const httpsUrl = "https://secure.example.com/auth";
|
|
|
|
redirectUser(httpsUrl);
|
|
|
|
expect(window.location.href).toBe(httpsUrl);
|
|
});
|
|
|
|
it("should handle http URLs", () => {
|
|
const httpUrl = "http://example.com/redirect";
|
|
|
|
redirectUser(httpUrl);
|
|
|
|
expect(window.location.href).toBe(httpUrl);
|
|
});
|
|
|
|
it("should handle relative paths", () => {
|
|
const relativePath = "/app/dashboard";
|
|
const replaceSpy = vi.spyOn(window.location, 'replace');
|
|
|
|
redirectUser(relativePath);
|
|
|
|
expect(replaceSpy).toHaveBeenCalledWith(relativePath);
|
|
});
|
|
|
|
it("should handle absolute paths without protocol", () => {
|
|
const absolutePath = "/admin/users";
|
|
const replaceSpy = vi.spyOn(window.location, 'replace');
|
|
|
|
redirectUser(absolutePath);
|
|
|
|
expect(replaceSpy).toHaveBeenCalledWith(absolutePath);
|
|
});
|
|
});
|
|
|
|
describe("logsErrorMessage", () => {
|
|
it("should return correct error message for known error codes", () => {
|
|
expect(logsErrorMessage(10001)).toBe("message.ServerInternalError");
|
|
expect(logsErrorMessage(20001)).toBe("message.SearchSQLNotValid");
|
|
expect(logsErrorMessage(20002)).toBe("message.SearchStreamNotFound");
|
|
expect(logsErrorMessage(20003)).toBe("message.FullTextSearchFieldNotFound");
|
|
expect(logsErrorMessage(20004)).toBe("message.SearchFieldNotFound");
|
|
});
|
|
|
|
it("should return empty string for unknown error codes", () => {
|
|
expect(logsErrorMessage(99999)).toBe("");
|
|
expect(logsErrorMessage(0)).toBe("");
|
|
expect(logsErrorMessage(-1)).toBe("");
|
|
});
|
|
|
|
it("should handle string inputs gracefully", () => {
|
|
// Even though TypeScript expects number, test runtime behavior
|
|
expect(logsErrorMessage("10001" as any)).toBe("message.ServerInternalError");
|
|
expect(logsErrorMessage("invalid" as any)).toBe("");
|
|
});
|
|
|
|
it("should handle null and undefined inputs", () => {
|
|
expect(logsErrorMessage(null as any)).toBe("");
|
|
expect(logsErrorMessage(undefined as any)).toBe("");
|
|
});
|
|
|
|
it("should return exact string matches", () => {
|
|
const message = logsErrorMessage(10001);
|
|
expect(typeof message).toBe("string");
|
|
expect(message).toBe("message.ServerInternalError");
|
|
});
|
|
});
|
|
|
|
describe("Integration Tests", () => {
|
|
it("should handle complete organization selection workflow", async () => {
|
|
const userInfo = {
|
|
email: "workflow@example.com",
|
|
name: "Workflow User"
|
|
};
|
|
|
|
const orgResponse = {
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 1,
|
|
name: "Workflow Org",
|
|
org_type: "default",
|
|
identifier: "workflow-org",
|
|
user_obj: { email: "workflow@example.com" },
|
|
ingest_threshold: 1000,
|
|
search_threshold: 500,
|
|
note: "Workflow organization"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(orgResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
await getDefaultOrganization(userInfo, "workflow-org");
|
|
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalledWith(
|
|
0, 100000, "id", false, "", "workflow-org"
|
|
);
|
|
});
|
|
|
|
it("should handle redirect with error handling", () => {
|
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
try {
|
|
redirectUser("https://valid-url.com");
|
|
expect(window.location.href).toBe("https://valid-url.com");
|
|
} catch (error) {
|
|
// Handle any potential errors gracefully
|
|
expect(consoleSpy).not.toHaveBeenCalled();
|
|
}
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it("should provide consistent error messaging across different error types", () => {
|
|
const serverErrors = [10001];
|
|
const searchErrors = [20001, 20002, 20003, 20004];
|
|
|
|
serverErrors.forEach(code => {
|
|
const message = logsErrorMessage(code);
|
|
expect(message).toBeTruthy();
|
|
expect(typeof message).toBe("string");
|
|
});
|
|
|
|
searchErrors.forEach(code => {
|
|
const message = logsErrorMessage(code);
|
|
expect(message).toBeTruthy();
|
|
expect(typeof message).toBe("string");
|
|
expect(message.toLowerCase()).toContain("search");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Error Handling", () => {
|
|
it("should handle network errors in getDefaultOrganization", async () => {
|
|
const networkError = new Error("Network connection failed");
|
|
mockOrganizationsService.os_list = vi.fn().mockRejectedValue(networkError);
|
|
|
|
await expect(getDefaultOrganization({email: "test@test.com"}, "org"))
|
|
.rejects.toThrow("Network connection failed");
|
|
});
|
|
|
|
it("should handle malformed organization data", async () => {
|
|
const malformedResponse = {
|
|
data: {
|
|
data: [
|
|
{
|
|
// Missing required fields
|
|
name: "Incomplete Org"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockOrganizationsService.os_list = vi.fn().mockResolvedValue(malformedResponse);
|
|
mockZincutils.useLocalOrganization = vi.fn().mockReturnValue({ value: null });
|
|
|
|
// Should not throw when processing malformed data
|
|
await getDefaultOrganization({email: "test@test.com"}, "org");
|
|
expect(mockOrganizationsService.os_list).toHaveBeenCalled();
|
|
});
|
|
});
|
|
}); |