feat(services): add live container tests and mark Task 2 complete in TODO.md

This commit is contained in:
Lucas Winther
2026-09-05 23:12:49 +02:00
parent ed38e5b537
commit 78ae79372c
2 changed files with 89 additions and 5 deletions
+4 -4
View File
@@ -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/`:
+85 -1
View File
@@ -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);
});
});