diff --git a/webapp/src/components/tabContentComponents/loggingContent.vue b/webapp/src/components/tabContentComponents/loggingContent.vue index 1802aa8d..f621a734 100644 --- a/webapp/src/components/tabContentComponents/loggingContent.vue +++ b/webapp/src/components/tabContentComponents/loggingContent.vue @@ -17,6 +17,7 @@ class="uk-button uk-button-default uk-width-1-1" type="button" @click="updateLogs()" + data-test-id="update-btn" > Refresh Logs @@ -27,6 +28,7 @@ :url="logFileURI" button-label="Download Log File" :button-primary="false" + data-test-id="download-btn" /> diff --git a/webapp/src/tests/unit/LoggingContent.spec.js b/webapp/src/tests/unit/LoggingContent.spec.js index e7934c1a..93a7aedc 100644 --- a/webapp/src/tests/unit/LoggingContent.spec.js +++ b/webapp/src/tests/unit/LoggingContent.spec.js @@ -2,24 +2,40 @@ import { shallowMount, flushPromises } from "@vue/test-utils"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { createTestingPinia } from "@pinia/testing"; import axios from "axios"; +import fs from "fs"; +import path from "path"; import LoggingContent from "../../components/tabContentComponents/loggingContent.vue"; // Mock Axios -vi.mock("axios"); +vi.mock("axios", () => { + return { + default: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + patch: vi.fn(), + }, + }; +}); // Mock VueUse to prevent IntersectionObserver crashes in JSDOM +// This will be removed once vue-route is implemented vi.mock("@vueuse/core", () => ({ useIntersectionObserver: vi.fn(() => ({ stop: vi.fn(), })), })); -describe("LoggingContent.vue", () => { +// Test Description +describe("Test LoggingContent.vue", () => { let wrapper; - // A sample log string matching backend's format - const mockLogData = `[2026-05-14 08:46:12,808] [INFO] OFM server root logger has been set up at INFO level`; + // Define path to a real log file and read it + const logFilePath = path.resolve(__dirname, "../fixtures/realLog.txt"); + const mockLogData = fs.readFileSync(logFilePath, "utf-8"); + // Things to do before each test beforeEach(async () => { // Reset mocks before each test vi.clearAllMocks(); @@ -41,22 +57,32 @@ describe("LoggingContent.vue", () => { ], // Simplify child components stubs: { - PaginateLinks: true, - EndpointButton: true, - transition: false, - teleport: true, - }, + PaginateLinks: true, + EndpointButton: true, + transition: true, + teleport: true, + settings: true, + }, }, }); - await flushPromises(); + // flush so we avoid leaving uncompleted processes running on background + await flushPromises(); }); - // Render check - it("renders the component correctly", () => { + // Tear down wrapper, unmount testing component + afterEach(() => { + wrapper.unmount(); + }); + + // Test 1: Render check, if things do exist in the page as intended + it("renders the component correctly", async () => { + // Check if component is been loaded and it is rendered expect(wrapper.exists()).toBe(true); + // Check if navbar exists expect(wrapper.find(".logging-navbar").exists()).toBe(true); }); + // Test 2: Check log parsing to see if specific INFO exists it("fetches logs and parses them correctly when updateLogs is called", async () => { // Trigger the method await wrapper.vm.updateLogs(); @@ -65,28 +91,60 @@ describe("LoggingContent.vue", () => { // Verify Axios was called with the correct URI from the Pinia store expect(axios.get).toHaveBeenCalledWith("http://microscope.local:5000/api/v3/log/"); - // Verify the logs array was populated and reversed (newest first) - expect(wrapper.vm.logs.length).toBe(1); + // Verify the logs have correct file length + // Helps validate formatting, etc + expect(wrapper.vm.logs.length).toBe(5491); - // Check if the most recent log (ERROR) is first due to .reverse() + // Check the most recent log expect(wrapper.vm.logs[0].level).toBe("INFO"); - // Check if the multi-line traceback was appended correctly to the ERROR log - expect(wrapper.vm.logs[0].message).toContain("OFM"); - expect(wrapper.vm.logs[0].summary).toContain("server"); + // Check if the multi-line file has real first connection information + expect(wrapper.vm.logs[0].message).toContain("uvicorn"); + expect(wrapper.vm.logs[0].summary).toContain("http://0.0.0.0:5000"); }); + // Test 3: Filter logging file information it("filters logs based on the selected level", async () => { await wrapper.vm.updateLogs(); - // Set filter to ERROR (should only show ERROR and CRITICAL) - wrapper.vm.filterLevel = "INFO"; - - // wait for Vue reactivity to update computed properties + // Set filter to WARNING wait and compare expected length + wrapper.vm.filterLevel = "WARNING"; await wrapper.vm.$nextTick(); + expect(wrapper.vm.filteredItems.length).toBe(1449); + expect(wrapper.vm.filteredItems[0].level).toBe("WARNING"); - // Only the 1 ERROR log should remain in filteredItems - expect(wrapper.vm.filteredItems.length).toBe(1); + // Set filter to INFO wait and compare expected length + wrapper.vm.filterLevel = "INFO"; + await wrapper.vm.$nextTick(); + expect(wrapper.vm.filteredItems.length).toBe(5491); expect(wrapper.vm.filteredItems[0].level).toBe("INFO"); + + // Set filter to ERROR wait and compare expected length + wrapper.vm.filterLevel = "ERROR"; + await wrapper.vm.$nextTick(); + expect(wrapper.vm.filteredItems.length).toBe(7); + expect(wrapper.vm.filteredItems[0].level).toBe("ERROR"); + }); + + // Test 4: Click buttons + // This required the component file to be modified by adding data-test-id flags. + // flags are needed as classes and text content may change by translation or style change. + it("click buttons", async () => { + // Click download button + const downloadButton = wrapper.find('[data-test-id="download-btn"]'); + expect(downloadButton.exists()).toBe(true); + await downloadButton.trigger("click"); + + // Click update button + const updateButton = wrapper.find('[data-test-id="update-btn"]'); + expect(updateButton.exists()).toBe(true); + await updateButton.trigger("click"); + }); + + // Test 5: Compare downloaded file to fixture file. + // Test 6: WIP + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 15)); }); }); diff --git a/webapp/vitest.config.js b/webapp/vitest.config.js index b1dd132c..372ea36b 100644 --- a/webapp/vitest.config.js +++ b/webapp/vitest.config.js @@ -5,16 +5,18 @@ import { defineConfig } from "vitest/config"; import vue from "@vitejs/plugin-vue"; import path from "path"; -console.log("\n\n::: Reading vitest config.") +console.log("\n\n::: Reading vitest config."); export default defineConfig({ - plugins: [vue({ + plugins: [ + vue({ template: { compilerOptions: { hoistStatic: false, }, }, - })], + }), + ], test: { environment: "jsdom", @@ -31,7 +33,7 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(__dirname, "./src"), - "vue": path.resolve(__dirname, "./node_modules/vue"), + vue: path.resolve(__dirname, "./node_modules/vue"), }, }, });