Files
openobserve/web/src/utils/common.spec.ts
T
Sai Nikhil 0709ca9e9d fix: added more unit test cases (#8025)
### **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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-6326b5fdbd0493710b156bfb06237d5906fc493de034e872a879b9d59245d483">+565/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>billings.spec.ts</strong><dd><code>Add billing workflows and
error tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-8152eb5ae97615b253cd61eedea9147bf255cf444eefea25e1a4e954ffc7ee65">+547/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>convertDataIntoUnitValue.spec.ts</strong><dd><code>Test data
unit conversion and formatting utils</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-e9d8a984ed78af13e4d4424c605a3a55fe6ad2a3f7e403fad156ce17ffdc3bdd">+452/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>organizations.spec.ts</strong><dd><code>Cover organizations
service endpoints and flows</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-d891fff7a737f937ff3cad0710f07a99479d7160e31928259d1aec4ebfaf367d">+519/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AddAlert.spec.ts</strong><dd><code>Add missing
composable/service mocks for alerts</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-84362398fea8abd9acf30f4f40ba8d4cf27348677454017bd4e6f568d5176ac6">+47/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>User.spec.js</strong><dd><code>Stabilize user tests and
adjust stubs</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-f792c36cfff4d60265030596a1316a3fe207a1bcafc53cd0223a4873f9daf25b">+8/-20</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>users.spec.ts</strong><dd><code>Add users service tests
(CRUD, roles)</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-1e0367915c28073772fd91455c185fa522f17453fc868207b030f606fb7c0dee">+425/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>common.spec.ts</strong><dd><code>Add common utilities unit
tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-37bf940c4e06c75c7443e4342a8e2896adbb8cff23fad8f7227e4cd58537e6fb">+395/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>useStreams.spec.ts</strong><dd><code>Add tests for
useStreams composable pagination</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-513e2d53c46c553904a054e9c2cd12d33e800afc54fe5b4192c9782382ecd79b">+285/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>auth.spec.ts</strong><dd><code>Add authentication service
tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-ff84b903292a1f76994704e04dc9a7aaf6788497f8b561b7f693f6146a93b140">+199/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AddUser.spec.js</strong><dd><code>Update and fix AddUser
tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-9f6b815996dc8497b76604e29e7495042466714655abc8b5010430959007f3d0">+62/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>ReportList.spec.js</strong><dd><code>Fix failing report list
tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-c3de3f1590ac7b4401cf5570a52f0dcf6471898e4ffc7ccbc19166ba600a181f">+46/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>CreateReport.spec.js</strong><dd><code>Fix create report
test failures</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-8a0f39751d08a258c4997c5e68edaf167ea01477528beee2295dd9f992310cc6">+61/-0</a>&nbsp;
&nbsp; </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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/8025/files#diff-fa72375a2848d474b311c07cf6aef7d6117b1f4d88f56a74c9207f39df72333c">+13/-3</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

</details>

___
2025-08-17 12:55:15 +05:30

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();
});
});
});