test: add comprehensive tests for tool workshop functionality

Backend tests:
- Unit tests for docker_build service (successful/failed builds, context, paths)
- Unit tests for readiness_probe service (success, timeout, retries, edge cases)
- Integration tests for config_folders API (CRUD + project overrides)
- Integration tests for tool_types API with new fields
- Integration tests for tool_configs API with new fields

Frontend tests:
- ToolWorkshopPage component tests (all 3 tabs, create/edit/delete)
- API client tests for tool_types and config_folders

Fixes:
- Add field_validator import to tool_configs.py
- Add JSON import to tool_config model
- Update frontend test button names to match UI (Create Tool Type, Add Config, Create Folder)

Quality gates: backend unit tests passing (23/23)
This commit is contained in:
Fusion
2026-05-22 19:45:10 +02:00
parent 8dd350286e
commit dacf105200
13 changed files with 2621 additions and 5 deletions
+147
View File
@@ -0,0 +1,147 @@
import { describe, expect, it, vi } from "vitest";
import axios from "axios";
import {
createConfigFolder,
deleteConfigFolder,
listConfigFolders,
updateConfigFolder,
} from "../api/config_folders";
vi.mock("axios");
const mockedAxios = vi.mocked(axios);
describe("config_folders API", () => {
describe("listConfigFolders", () => {
it("returns folders with files and overrides", async () => {
const mockResponse = {
data: [
{
id: "folder-1",
name: "my-dotfiles",
mount_path: "/home/user",
files: {
".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"",
},
project_overrides: {
"proj-1": {
mount_path: "/workspace",
files: { ".zshrc": "different content" },
},
},
is_active: true,
},
],
};
mockedAxios.get.mockResolvedValue(mockResponse);
const result = await listConfigFolders();
expect(result).toHaveLength(1);
expect(result[0].name).toBe("my-dotfiles");
expect(result[0].files).toEqual({
".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"",
});
expect(result[0].project_overrides).toEqual({
"proj-1": {
mount_path: "/workspace",
files: { ".zshrc": "different content" },
},
});
});
it("returns empty array when no folders", async () => {
mockedAxios.get.mockResolvedValue({ data: [] });
const result = await listConfigFolders();
expect(result).toEqual([]);
});
});
describe("createConfigFolder", () => {
it("creates folder with files", async () => {
const mockResponse = {
data: {
id: "new-folder",
name: "my-configs",
mount_path: "/home/user",
files: { "test.txt": "hello" },
is_active: true,
},
};
mockedAxios.post.mockResolvedValue(mockResponse);
const result = await createConfigFolder({
name: "my-configs",
mount_path: "/home/user",
files: { "test.txt": "hello" },
});
expect(result.name).toBe("my-configs");
expect(result.files).toEqual({ "test.txt": "hello" });
expect(mockedAxios.post).toHaveBeenCalledWith(
"/config-folders",
expect.objectContaining({
name: "my-configs",
mount_path: "/home/user",
files: { "test.txt": "hello" },
})
);
});
});
describe("updateConfigFolder", () => {
it("updates folder files", async () => {
const mockResponse = {
data: {
id: "folder-1",
name: "updated-name",
files: { "new.txt": "content" },
},
};
mockedAxios.put.mockResolvedValue(mockResponse);
const result = await updateConfigFolder("folder-1", {
name: "updated-name",
files: { "new.txt": "content" },
});
expect(result.name).toBe("updated-name");
expect(mockedAxios.put).toHaveBeenCalledWith(
"/config-folders/folder-1",
expect.objectContaining({
name: "updated-name",
files: { "new.txt": "content" },
})
);
});
it("updates folder activation status", async () => {
const mockResponse = {
data: {
id: "folder-1",
name: "my-configs",
is_active: false,
},
};
mockedAxios.put.mockResolvedValue(mockResponse);
const result = await updateConfigFolder("folder-1", {
is_active: false,
});
expect(result.is_active).toBe(false);
});
});
describe("deleteConfigFolder", () => {
it("deletes folder", async () => {
mockedAxios.delete.mockResolvedValue({ data: undefined });
await deleteConfigFolder("folder-1");
expect(mockedAxios.delete).toHaveBeenCalledWith("/config-folders/folder-1");
});
});
});