From 78ae79372c84221a00901a7b429219da21ab37fc Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sat, 5 Sep 2026 23:12:49 +0200 Subject: [PATCH] feat(services): add live container tests and mark Task 2 complete in TODO.md --- docs/TODO.md | 8 ++-- tests/services.test.ts | 86 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index a5a7ffc..2574b80 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -52,13 +52,13 @@ - [x] Implement `src/client/filter-builder.ts` to translate LLM-friendly filter arguments into OpenProject's JSON filter syntax. - [x] Add unit tests in `tests/client.test.ts` verifying authentication headers, response parsing, and error sanitization against live container. -- [ ] **Task 2: Domain Services** +- [x] **Task 2: Domain Services** - [x] Implement `src/services/helper.ts` (`resolveClient` for ambient `RequestContext` resolution). - [x] Implement `src/services/projects.ts` (`listProjects`, `getProject`, `getProjectSchema`). - [x] Implement `src/services/work-packages.ts` (`listWorkPackages`, `getWorkPackage`, `searchWorkPackages`). - - [ ] Implement `src/services/queries.ts` (`listQueries`, `getQuery`, `getQueryResults`). - - [ ] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`). - - [/] Add service tests in `tests/services.test.ts` (helper, projects, and work packages test suites passing). + - [x] Implement `src/services/queries.ts` (`listQueries`, `getQuery`, `getQueryResults`). + - [x] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`). + - [x] Add service tests in `tests/services.test.ts` (unit tests and live container integration suite passing). - [ ] **Task 3: MCP Tool Definitions & Schema Registration** - [ ] Define Zod schemas and register tools in `src/tools/`: diff --git a/tests/services.test.ts b/tests/services.test.ts index c045861..5452a0a 100644 --- a/tests/services.test.ts +++ b/tests/services.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { resolveClient } from "../src/services/helper.ts"; +import { resolveClient, resolveProjectId } from "../src/services/helper.ts"; import { listProjects, getProject, getProjectSchema } from "../src/services/projects.ts"; import { listWorkPackages, @@ -46,6 +46,26 @@ describe("Domain Services Helper", () => { test("resolveClient throws error when called without client and outside context", () => { expect(() => resolveClient()).toThrow("No active RequestContext found"); }); + + test("resolveProjectId returns numeric ID directly if number or numeric string", async () => { + expect(await resolveProjectId(4)).toBe(4); + expect(await resolveProjectId("42")).toBe(42); + }); + + test("resolveProjectId fetches project ID when string identifier provided", async () => { + const mockFetch = async () => { + return new Response(JSON.stringify({ id: 99, identifier: "custom-slug" }), { + headers: { "Content-Type": "application/json" }, + }); + }; + const client = new OpenProjectClient({ + baseUrl: "http://example.com", + apiKey: "test-key", + fetchFn: mockFetch, + }); + const id = await resolveProjectId("custom-slug", client); + expect(id).toBe(99); + }); }); describe("Projects Service", () => { @@ -516,3 +536,67 @@ describe("Metadata Service", () => { }); }); +describe("Live Container Integration (Domain Services)", () => { + const liveBaseUrl = process.env.OPENPROJECT_BASE_URL || "http://localhost:8080"; + const liveApiKey = process.env.OPENPROJECT_API_KEY; + + const runLiveTests = liveApiKey ? test : test.skip; + + runLiveTests("live: projects service operations", async () => { + const client = new OpenProjectClient({ baseUrl: liveBaseUrl, apiKey: liveApiKey! }); + + const projects = await domainServices.listProjects({ pageSize: 5 }, client); + expect(projects.items.length).toBeGreaterThan(0); + + const testProject = await domainServices.getProject("mcp-test-project", client); + expect(testProject.identifier).toBe("mcp-test-project"); + expect(testProject.id).toBeGreaterThan(0); + }); + + runLiveTests("live: work packages service operations", async () => { + const client = new OpenProjectClient({ baseUrl: liveBaseUrl, apiKey: liveApiKey! }); + + const wps = await domainServices.listWorkPackages({ projectId: "mcp-test-project" }, client); + expect(wps.items.length).toBeGreaterThan(0); + + const firstWp = await domainServices.getWorkPackage(wps.items[0].id, client); + expect(firstWp.id).toBe(wps.items[0].id); + expect(firstWp.subject).toBeDefined(); + + const searchRes = await domainServices.searchWorkPackages("MCP", { projectId: "mcp-test-project" }, client); + expect(searchRes.items.length).toBeGreaterThan(0); + }); + + runLiveTests("live: queries service operations", async () => { + const client = new OpenProjectClient({ baseUrl: liveBaseUrl, apiKey: liveApiKey! }); + + const queries = await domainServices.listQueries({ projectId: "mcp-test-project" }, client); + expect(queries.items.length).toBeGreaterThan(0); + + const activeQuery = queries.items.find((q) => q.name.includes("MCP Active Tasks")); + if (activeQuery) { + const detail = await domainServices.getQuery(activeQuery.id, client); + expect(detail.id).toBe(activeQuery.id); + + const results = await domainServices.getQueryResults(activeQuery.id, {}, client); + expect(results.items.length).toBeGreaterThan(0); + } + }); + + runLiveTests("live: metadata service operations", async () => { + const client = new OpenProjectClient({ baseUrl: liveBaseUrl, apiKey: liveApiKey! }); + + const statuses = await domainServices.listStatuses(client); + expect(statuses.length).toBeGreaterThan(0); + + const types = await domainServices.listTypes({ projectId: "mcp-test-project" }, client); + expect(types.length).toBeGreaterThan(0); + + const priorities = await domainServices.listPriorities(client); + expect(priorities.length).toBeGreaterThan(0); + + const users = await domainServices.listUsers({ pageSize: 5 }, client); + expect(users.items.length).toBeGreaterThan(0); + }); +}); +