diff --git a/apps/web/src/api/config_folders.test.ts b/apps/web/src/api/config_folders.test.ts
index ac6828e..9f32449 100644
--- a/apps/web/src/api/config_folders.test.ts
+++ b/apps/web/src/api/config_folders.test.ts
@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from "vitest";
-import axios from "axios";
import {
createConfigFolder,
deleteConfigFolder,
@@ -8,8 +7,25 @@ import {
updateConfigFolder,
} from "../api/config_folders";
-vi.mock("axios");
-const mockedAxios = vi.mocked(axios);
+const mockGet = vi.fn();
+const mockPost = vi.fn();
+const mockPut = vi.fn();
+const mockDelete = vi.fn();
+
+vi.mock("../api/client", () => ({
+ apiClient: {
+ get: (...args: unknown[]) => mockGet(...args),
+ post: (...args: unknown[]) => mockPost(...args),
+ put: (...args: unknown[]) => mockPut(...args),
+ delete: (...args: unknown[]) => mockDelete(...args),
+ interceptors: {
+ response: {
+ use: vi.fn(),
+ },
+ },
+ },
+ shouldSkipAuthRedirect: vi.fn(() => false),
+}));
describe("config_folders API", () => {
describe("listConfigFolders", () => {
@@ -19,43 +35,24 @@ describe("config_folders API", () => {
{
id: "folder-1",
name: "my-dotfiles",
+ description: "My personal config files",
mount_path: "/home/user",
- files: {
- ".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"",
- },
- project_overrides: {
- "proj-1": {
- mount_path: "/workspace",
- files: { ".zshrc": "different content" },
- },
- },
+ files: { ".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"" },
+ project_overrides: {},
is_active: true,
+ user_id: "user-1",
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
],
};
- mockedAxios.get.mockResolvedValue(mockResponse);
+ mockGet.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([]);
+ expect(result[0].files).toEqual({ ".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"" });
+ expect(mockGet).toHaveBeenCalledWith("/config-folders");
});
});
@@ -63,29 +60,30 @@ describe("config_folders API", () => {
it("creates folder with files", async () => {
const mockResponse = {
data: {
- id: "new-folder",
- name: "my-configs",
- mount_path: "/home/user",
- files: { "test.txt": "hello" },
+ id: "folder-new",
+ name: "new-folder",
+ mount_path: "/workspace",
+ files: { ".env": "API_URL=http://localhost" },
is_active: true,
+ user_id: "user-1",
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
};
- mockedAxios.post.mockResolvedValue(mockResponse);
+ mockPost.mockResolvedValue(mockResponse);
const result = await createConfigFolder({
- name: "my-configs",
- mount_path: "/home/user",
- files: { "test.txt": "hello" },
+ name: "new-folder",
+ mount_path: "/workspace",
+ files: { ".env": "API_URL=http://localhost" },
});
- expect(result.name).toBe("my-configs");
- expect(result.files).toEqual({ "test.txt": "hello" });
- expect(mockedAxios.post).toHaveBeenCalledWith(
+ expect(result.name).toBe("new-folder");
+ expect(mockPost).toHaveBeenCalledWith(
"/config-folders",
expect.objectContaining({
- name: "my-configs",
- mount_path: "/home/user",
- files: { "test.txt": "hello" },
+ name: "new-folder",
+ mount_path: "/workspace",
})
);
});
@@ -96,52 +94,38 @@ describe("config_folders API", () => {
const mockResponse = {
data: {
id: "folder-1",
- name: "updated-name",
- files: { "new.txt": "content" },
+ name: "updated-folder",
+ mount_path: "/home/user",
+ files: { ".bashrc": "alias ll='ls -la'" },
+ is_active: true,
+ user_id: "user-1",
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
};
- mockedAxios.put.mockResolvedValue(mockResponse);
+ mockPut.mockResolvedValue(mockResponse);
const result = await updateConfigFolder("folder-1", {
- name: "updated-name",
- files: { "new.txt": "content" },
+ files: { ".bashrc": "alias ll='ls -la'" },
});
- expect(result.name).toBe("updated-name");
- expect(mockedAxios.put).toHaveBeenCalledWith(
+ expect(result.files).toEqual({ ".bashrc": "alias ll='ls -la'" });
+ expect(mockPut).toHaveBeenCalledWith(
"/config-folders/folder-1",
expect.objectContaining({
- name: "updated-name",
- files: { "new.txt": "content" },
+ files: { ".bashrc": "alias ll='ls -la'" },
})
);
});
-
- 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 });
+ mockDelete.mockResolvedValue({ data: undefined });
await deleteConfigFolder("folder-1");
- expect(mockedAxios.delete).toHaveBeenCalledWith("/config-folders/folder-1");
+ expect(mockDelete).toHaveBeenCalledWith("/config-folders/folder-1");
});
});
});
diff --git a/apps/web/src/api/tool_types.test.ts b/apps/web/src/api/tool_types.test.ts
index 99a0681..8609907 100644
--- a/apps/web/src/api/tool_types.test.ts
+++ b/apps/web/src/api/tool_types.test.ts
@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from "vitest";
-import axios from "axios";
import {
createToolType,
deleteToolType,
@@ -9,8 +8,25 @@ import {
validateToolType,
} from "../api/tool_types";
-vi.mock("axios");
-const mockedAxios = vi.mocked(axios);
+const mockGet = vi.fn();
+const mockPost = vi.fn();
+const mockPut = vi.fn();
+const mockDelete = vi.fn();
+
+vi.mock("../api/client", () => ({
+ apiClient: {
+ get: (...args: unknown[]) => mockGet(...args),
+ post: (...args: unknown[]) => mockPost(...args),
+ put: (...args: unknown[]) => mockPut(...args),
+ delete: (...args: unknown[]) => mockDelete(...args),
+ interceptors: {
+ response: {
+ use: vi.fn(),
+ },
+ },
+ },
+ shouldSkipAuthRedirect: vi.fn(() => false),
+}));
describe("tool_types API", () => {
describe("listToolTypes", () => {
@@ -28,10 +44,13 @@ describe("tool_types API", () => {
timeout: 30,
interval: 2,
},
+ build_context: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
],
};
- mockedAxios.get.mockResolvedValue(mockResponse);
+ mockGet.mockResolvedValue(mockResponse);
const result = await listToolTypes();
@@ -53,10 +72,13 @@ describe("tool_types API", () => {
definition_type: "compose",
compose_template: "version: '3.8'",
dockerfile_template: null,
+ build_context: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
],
};
- mockedAxios.get.mockResolvedValue(mockResponse);
+ mockGet.mockResolvedValue(mockResponse);
const result = await listToolTypes();
@@ -73,9 +95,12 @@ describe("tool_types API", () => {
name: "docker-tool",
definition_type: "dockerfile",
dockerfile_template: "FROM node:18",
+ build_context: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
};
- mockedAxios.post.mockResolvedValue(mockResponse);
+ mockPost.mockResolvedValue(mockResponse);
const result = await createToolType({
name: "docker-tool",
@@ -87,7 +112,7 @@ describe("tool_types API", () => {
});
expect(result.definition_type).toBe("dockerfile");
- expect(mockedAxios.post).toHaveBeenCalledWith(
+ expect(mockPost).toHaveBeenCalledWith(
"/tool-types",
expect.objectContaining({
definition_type: "dockerfile",
@@ -106,9 +131,12 @@ describe("tool_types API", () => {
timeout: 60,
interval: 3,
},
+ build_context: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
};
- mockedAxios.post.mockResolvedValue(mockResponse);
+ mockPost.mockResolvedValue(mockResponse);
const result = await createToolType({
name: "probed-tool",
@@ -132,30 +160,25 @@ describe("tool_types API", () => {
});
describe("validateToolType", () => {
- it("validates compose template", async () => {
+ it("validates tool type by id", async () => {
const mockResponse = {
data: { valid: true, errors: [] },
};
- mockedAxios.post.mockResolvedValue(mockResponse);
+ mockGet.mockResolvedValue(mockResponse);
- const result = await validateToolType({
- definition_type: "compose",
- compose_template: "version: '3.8'",
- });
+ const result = await validateToolType("type-1");
expect(result.valid).toBe(true);
+ expect(mockGet).toHaveBeenCalledWith("/tool-types/type-1/validate");
});
it("returns validation errors", async () => {
const mockResponse = {
data: { valid: false, errors: ["Invalid YAML"] },
};
- mockedAxios.post.mockResolvedValue(mockResponse);
+ mockGet.mockResolvedValue(mockResponse);
- const result = await validateToolType({
- definition_type: "compose",
- compose_template: "invalid: yaml: [",
- });
+ const result = await validateToolType("type-1");
expect(result.valid).toBe(false);
expect(result.errors).toContain("Invalid YAML");
@@ -170,9 +193,12 @@ describe("tool_types API", () => {
name: "updated-tool",
definition_type: "dockerfile",
dockerfile_template: "FROM python:3.11",
+ build_context: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
};
- mockedAxios.put.mockResolvedValue(mockResponse);
+ mockPut.mockResolvedValue(mockResponse);
const result = await updateToolType("type-1", {
definition_type: "dockerfile",
@@ -180,7 +206,7 @@ describe("tool_types API", () => {
});
expect(result.definition_type).toBe("dockerfile");
- expect(mockedAxios.put).toHaveBeenCalledWith(
+ expect(mockPut).toHaveBeenCalledWith(
"/tool-types/type-1",
expect.objectContaining({
definition_type: "dockerfile",
@@ -191,11 +217,11 @@ describe("tool_types API", () => {
describe("deleteToolType", () => {
it("deletes tool type", async () => {
- mockedAxios.delete.mockResolvedValue({ data: undefined });
+ mockDelete.mockResolvedValue({ data: undefined });
await deleteToolType("type-1");
- expect(mockedAxios.delete).toHaveBeenCalledWith("/tool-types/type-1");
+ expect(mockDelete).toHaveBeenCalledWith("/tool-types/type-1");
});
});
});
diff --git a/apps/web/src/pages/tool-workshop.test.tsx b/apps/web/src/pages/tool-workshop.test.tsx
index 63967d7..cbc999a 100644
--- a/apps/web/src/pages/tool-workshop.test.tsx
+++ b/apps/web/src/pages/tool-workshop.test.tsx
@@ -18,10 +18,13 @@ const mockToolTypes = [
definition_type: "compose",
compose_template: "version: '3.8'\\nservices:\\n app:\\n image: codercom/code-server",
dockerfile_template: null,
+ build_context: null,
readiness_probe: null,
required_variables: ["REPO_PATH"],
is_builtin: true,
created_by_id: null,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
{
id: "type-2",
@@ -34,6 +37,7 @@ const mockToolTypes = [
definition_type: "dockerfile",
compose_template: null,
dockerfile_template: "FROM python:3.11",
+ build_context: null,
readiness_probe: {
command: "python --version",
timeout: 30,
@@ -42,6 +46,8 @@ const mockToolTypes = [
required_variables: [],
is_builtin: false,
created_by_id: "user-1",
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
];
@@ -49,6 +55,7 @@ const mockConfigs = [
{
id: "config-1",
tool_type_id: "type-1",
+ project_id: null,
key: "OPENAI_API_KEY",
value: "sk-test123",
config_type: "env",
@@ -62,9 +69,11 @@ const mockConfigs = [
{
id: "config-2",
tool_type_id: "type-2",
+ project_id: null,
key: "advanced-config",
value: "test-value",
config_type: "env",
+ file_path: null,
port_override: 9090,
start_command: "python app.py",
working_directory: "/app",
@@ -76,15 +85,19 @@ const mockConfigs = [
const mockFolders = [
{
id: "folder-1",
+ user_id: "user-1",
name: "my-dotfiles",
description: "My personal config files",
mount_path: "/home/user",
files: { ".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"" },
project_overrides: {},
is_active: true,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
{
id: "folder-2",
+ user_id: "user-1",
name: "project-configs",
description: "Project specific configs",
mount_path: "/workspace",
@@ -96,6 +109,8 @@ const mockFolders = [
},
},
is_active: false,
+ created_at: "2024-01-01T00:00:00Z",
+ updated_at: "2024-01-01T00:00:00Z",
},
];
@@ -115,9 +130,9 @@ describe("ToolWorkshopPage", () => {
});
it("renders tool types tab by default", async () => {
- vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes);
- vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs);
- vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders);
+ vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
+ vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
+ vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);
render(