import flushPromises from "flush-promises"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { RegisteredUser } from "@/api"; import { useServerMock } from "@/api/client/__mocks__"; import type { ServiceCredentialGroupResponse, ServiceCredentialsDefinition, ServiceCredentialsIdentifier, UserServiceCredentialsResponse, } from "@/api/userCredentials"; import { setupTestPinia } from "@/stores/testUtils"; import { useToolsServiceCredentialsDefinitionsStore } from "@/stores/toolsServiceCredentialsDefinitionsStore"; import { useUserStore } from "@/stores/userStore"; import { useUserToolsServiceCredentialsStore } from "@/stores/userToolsServiceCredentialsStore"; import { useUserToolCredentials } from "./userToolCredentials"; // Mock data const TEST_TOOL_ID = "test-tool"; const TEST_TOOL_VERSION = "1.0.0"; const TEST_USER_ID = "test-user-123"; const TEST_SERVICE_DEFINITION: ServiceCredentialsDefinition = { name: "aws-s3", version: "1.0", description: "AWS S3 Service", label: "AWS S3", optional: false, variables: [ { name: "bucket_name", label: "Bucket Name", description: "The S3 bucket name", optional: false, }, ], secrets: [ { name: "access_key", label: "Access Key", description: "AWS Access Key", optional: false, }, { name: "secret_key", label: "Secret Key", description: "AWS Secret Key", optional: false, }, ], }; const TEST_OPTIONAL_SERVICE_DEFINITION: ServiceCredentialsDefinition = { name: "azure-blob", version: "1.0", description: "Azure Blob Storage", label: "Azure Blob", optional: true, variables: [ { name: "account_name", label: "Account Name", description: "Azure storage account name", optional: false, }, ], secrets: [ { name: "account_key", label: "Account Key", description: "Azure storage account key", optional: false, }, ], }; const TEST_CREDENTIALS_GROUP: ServiceCredentialGroupResponse = { id: "group-123", name: "Test Group", update_time: "2023-01-01T00:00:00Z", variables: [ { name: "bucket_name", value: "my-test-bucket", }, ], secrets: [ { name: "access_key", is_set: true, }, { name: "secret_key", is_set: true, }, ], }; const TEST_USER_SOURCE_SERVICE: UserServiceCredentialsResponse = { id: "service-123", user_id: TEST_USER_ID, source_type: "tool", source_id: TEST_TOOL_ID, source_version: TEST_TOOL_VERSION, name: "aws-s3", version: "1.0", current_group_id: "group-123", groups: [TEST_CREDENTIALS_GROUP], }; const TEST_USER_SOURCE_SERVICE_NO_CURRENT_GROUP: UserServiceCredentialsResponse = { id: "service-456", user_id: TEST_USER_ID, source_type: "tool", source_id: TEST_TOOL_ID, source_version: TEST_TOOL_VERSION, name: "azure-blob", version: "1.0", current_group_id: null, groups: [ { id: "group-456", name: "Azure Group", update_time: "2023-01-01T00:00:00Z", variables: [{ name: "account_name", value: "test-account" }], secrets: [{ name: "account_key", is_set: false }], }, ], }; const TEST_CURRENT_USER: RegisteredUser = { id: TEST_USER_ID, email: "test@example.com", username: "testuser", is_admin: false, preferences: {}, total_disk_usage: 0, nice_total_disk_usage: "0 bytes", quota_percent: 0, quota: "0 bytes", deleted: false, purged: false, isAnonymous: false as const, }; // Mock the server responses const { server, http } = useServerMock(); describe("useUserToolCredentials", () => { let userStore: ReturnType; let toolsServiceCredentialsDefinitionsStore: ReturnType; let userToolsServiceCredentialsStore: ReturnType; beforeEach(() => { setupTestPinia(); userStore = useUserStore(); toolsServiceCredentialsDefinitionsStore = useToolsServiceCredentialsDefinitionsStore(); userToolsServiceCredentialsStore = useUserToolsServiceCredentialsStore(); // Set up current user userStore.currentUser = TEST_CURRENT_USER; // Set up service definitions toolsServiceCredentialsDefinitionsStore.setToolServiceCredentialsDefinitionFor( TEST_TOOL_ID, TEST_TOOL_VERSION, [TEST_SERVICE_DEFINITION, TEST_OPTIONAL_SERVICE_DEFINITION], ); // Mock API endpoints server.use( http.get("/api/users/{user_id}/credentials", ({ query, response }) => { const sourceType = query.get("source_type"); const sourceId = query.get("source_id"); const sourceVersion = query.get("source_version"); if (sourceType === "tool" && sourceId === TEST_TOOL_ID && sourceVersion === TEST_TOOL_VERSION) { return response(200).json([TEST_USER_SOURCE_SERVICE, TEST_USER_SOURCE_SERVICE_NO_CURRENT_GROUP]); } return response(200).json([]); }), http.post("/api/users/{user_id}/credentials", ({ response }) => { return response(200).json(TEST_CREDENTIALS_GROUP); }), http.put("/api/users/{user_id}/credentials/{user_credentials_id}/groups/{group_id}", ({ response }) => { return response(200).json({ ...TEST_CREDENTIALS_GROUP, variables: [{ name: "bucket_name", value: "updated-bucket" }], }); }), http.delete("/api/users/{user_id}/credentials/{user_credentials_id}/groups/{group_id}", ({ response }) => { return response(204).empty(); }), http.put("/api/users/{user_id}/credentials", ({ response }) => { return response(204).empty(); }), ); }); afterEach(() => { server.resetHandlers(); }); describe("initialization", () => { it("should initialize with correct tool ID and version", () => { const { sourceCredentialsDefinition } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(sourceCredentialsDefinition.value.sourceType).toBe("tool"); expect(sourceCredentialsDefinition.value.sourceId).toBe(TEST_TOOL_ID); expect(sourceCredentialsDefinition.value.services.size).toBe(2); expect(sourceCredentialsDefinition.value.services.has("aws-s3-1.0")).toBe(true); expect(sourceCredentialsDefinition.value.services.has("azure-blob-1.0")).toBe(true); }); it("should have proper reactive state initially", () => { const { currentUserToolServices, hasUserProvidedAllServiceCredentials, hasUserProvidedAllRequiredServiceCredentials, toolHasRequiredServiceCredentials, statusVariant, } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(currentUserToolServices.value).toBeUndefined(); expect(hasUserProvidedAllServiceCredentials.value).toBe(false); expect(hasUserProvidedAllRequiredServiceCredentials.value).toBe(false); expect(toolHasRequiredServiceCredentials.value).toBe(true); expect(statusVariant.value).toBe("warning"); }); }); describe("checkUserCredentials", () => { it("should fetch user credentials successfully", async () => { const { checkUserCredentials, currentUserToolServices } = useUserToolCredentials( TEST_TOOL_ID, TEST_TOOL_VERSION, ); await checkUserCredentials(); await flushPromises(); expect(currentUserToolServices.value).toHaveLength(2); expect(currentUserToolServices.value![0]).toEqual(TEST_USER_SOURCE_SERVICE); expect(currentUserToolServices.value![1]).toEqual(TEST_USER_SOURCE_SERVICE_NO_CURRENT_GROUP); }); it("should not fetch if user is not registered", async () => { userStore.currentUser = { isAnonymous: true, total_disk_usage: 0, nice_total_disk_usage: "0 bytes" }; const { checkUserCredentials, currentUserToolServices } = useUserToolCredentials( TEST_TOOL_ID, TEST_TOOL_VERSION, ); await checkUserCredentials(); await flushPromises(); expect(currentUserToolServices.value).toBeUndefined(); }); it("should not fetch if credentials already exist", async () => { const { checkUserCredentials } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); // First call await checkUserCredentials(); await flushPromises(); // Mock the store to track if fetch was called again const fetchSpy = vi.spyOn(userToolsServiceCredentialsStore, "fetchAllUserToolServices"); // Second call await checkUserCredentials(); await flushPromises(); expect(fetchSpy).not.toHaveBeenCalled(); }); }); describe("computed properties", () => { beforeEach(async () => { const { checkUserCredentials } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); await checkUserCredentials(); await flushPromises(); }); it("should correctly compute userServiceFor", () => { const { userServiceFor } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); const serviceIdentifier: ServiceCredentialsIdentifier = { name: "aws-s3", version: "1.0" }; const service = userServiceFor.value(serviceIdentifier); expect(service).toEqual(TEST_USER_SOURCE_SERVICE); }); it("should correctly compute userServiceGroupsFor", () => { const { userServiceGroupsFor } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); const serviceIdentifier: ServiceCredentialsIdentifier = { name: "aws-s3", version: "1.0" }; const groups = userServiceGroupsFor.value(serviceIdentifier); expect(groups).toEqual([TEST_CREDENTIALS_GROUP]); }); it("should correctly compute hasUserProvidedAllServiceCredentials", () => { const { hasUserProvidedAllServiceCredentials } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); // One service has current group, one doesn't expect(hasUserProvidedAllServiceCredentials.value).toBe(false); }); it("should correctly compute hasUserProvidedAllRequiredServiceCredentials", () => { const { hasUserProvidedAllRequiredServiceCredentials } = useUserToolCredentials( TEST_TOOL_ID, TEST_TOOL_VERSION, ); // Required service (aws-s3) has current group, optional service (azure-blob) doesn't expect(hasUserProvidedAllRequiredServiceCredentials.value).toBe(true); }); it("should correctly compute hasUserProvidedSomeOptionalServiceCredentials", () => { const { hasUserProvidedSomeOptionalServiceCredentials } = useUserToolCredentials( TEST_TOOL_ID, TEST_TOOL_VERSION, ); // Optional service (azure-blob) doesn't have current group expect(hasUserProvidedSomeOptionalServiceCredentials.value).toBe(false); }); it("should correctly compute toolHasRequiredServiceCredentials", () => { const { toolHasRequiredServiceCredentials } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(toolHasRequiredServiceCredentials.value).toBe(true); }); it("should correctly compute statusVariant", () => { const { statusVariant } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); // Should be success since required credentials are provided expect(statusVariant.value).toBe("success"); }); }); describe("utility functions", () => { it("should get service credentials definition by key", () => { const { getToolServiceCredentialsDefinitionFor } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); const serviceIdentifier: ServiceCredentialsIdentifier = { name: "aws-s3", version: "1.0" }; const definition = getToolServiceCredentialsDefinitionFor(serviceIdentifier); expect(definition).toEqual(TEST_SERVICE_DEFINITION); }); it("should throw error for non-existent service definition", () => { const { getToolServiceCredentialsDefinitionFor } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); const serviceIdentifier: ServiceCredentialsIdentifier = { name: "non-existent", version: "1.0" }; expect(() => getToolServiceCredentialsDefinitionFor(serviceIdentifier)).toThrow( `No definition found for credential service 'non-existent-1.0' in tool ${TEST_TOOL_ID}@${TEST_TOOL_VERSION}`, ); }); it("should build groups from user credentials", () => { const { buildGroupsFromUserCredentials } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); const groups = buildGroupsFromUserCredentials(TEST_SERVICE_DEFINITION, TEST_USER_SOURCE_SERVICE); expect(groups).toHaveLength(1); expect(groups[0]?.name).toBe("Test Group"); expect(groups[0]?.variables).toHaveLength(1); expect(groups[0]?.variables[0]?.name).toBe("bucket_name"); expect(groups[0]?.variables[0]?.value).toBe("my-test-bucket"); expect(groups[0]?.secrets).toHaveLength(2); expect(groups[0]?.secrets[0]?.name).toBe("access_key"); expect(groups[0]?.secrets[0]?.value).toBe("********"); }); }); describe("status variant computation", () => { it("should return info when busy", () => { // Mock the isBusy property to return true vi.spyOn(userToolsServiceCredentialsStore, "isBusy", "get").mockReturnValue(true); const { statusVariant } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(statusVariant.value).toBe("info"); }); it("should return success when all credentials provided", async () => { // Set up both services with current groups const serviceWithCurrentGroup: UserServiceCredentialsResponse = { ...TEST_USER_SOURCE_SERVICE_NO_CURRENT_GROUP, current_group_id: "group-456", }; userToolsServiceCredentialsStore.userToolsServices[`${TEST_USER_ID}-${TEST_TOOL_ID}-${TEST_TOOL_VERSION}`] = [TEST_USER_SOURCE_SERVICE, serviceWithCurrentGroup]; const { statusVariant } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(statusVariant.value).toBe("success"); }); it("should return warning when not all credentials provided", () => { userToolsServiceCredentialsStore.userToolsServices[`${TEST_USER_ID}-${TEST_TOOL_ID}-${TEST_TOOL_VERSION}`] = [TEST_USER_SOURCE_SERVICE_NO_CURRENT_GROUP]; const { statusVariant } = useUserToolCredentials(TEST_TOOL_ID, TEST_TOOL_VERSION); expect(statusVariant.value).toBe("warning"); }); }); });