fix(#297): Implement actual query processing for federation
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Added query processing to route federation queries to domain services: - Created query parser to extract intent and parameters from query strings - Route queries to TasksService, EventsService, and ProjectsService - Return actual data instead of placeholder responses - Added workspace context validation Implemented query types: - Tasks: "get tasks", "show tasks", etc. - Events: "get events", "upcoming events", etc. - Projects: "get projects", "show projects", etc. Added 5 new tests for query processing (20 tests total, all passing): - Process tasks/events/projects queries - Handle unknown query types - Enforce workspace context requirements Updated FederationModule to import TasksModule, EventsModule, ProjectsModule. Fixes #297 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,9 @@ import { QueryService } from "./query.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { FederationService } from "./federation.service";
|
||||
import { SignatureService } from "./signature.service";
|
||||
import { TasksService } from "../tasks/tasks.service";
|
||||
import { EventsService } from "../events/events.service";
|
||||
import { ProjectsService } from "../projects/projects.service";
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { of, throwError } from "rxjs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
@@ -60,6 +63,18 @@ describe("QueryService", () => {
|
||||
get: vi.fn(),
|
||||
};
|
||||
|
||||
const mockTasksService = {
|
||||
findAll: vi.fn(),
|
||||
};
|
||||
|
||||
const mockEventsService = {
|
||||
findAll: vi.fn(),
|
||||
};
|
||||
|
||||
const mockProjectsService = {
|
||||
findAll: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -69,6 +84,9 @@ describe("QueryService", () => {
|
||||
{ provide: SignatureService, useValue: mockSignatureService },
|
||||
{ provide: HttpService, useValue: mockHttpService },
|
||||
{ provide: ConfigService, useValue: mockConfig },
|
||||
{ provide: TasksService, useValue: mockTasksService },
|
||||
{ provide: EventsService, useValue: mockEventsService },
|
||||
{ provide: ProjectsService, useValue: mockProjectsService },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -78,6 +96,20 @@ describe("QueryService", () => {
|
||||
signatureService = module.get<SignatureService>(SignatureService);
|
||||
httpService = module.get<HttpService>(HttpService);
|
||||
|
||||
// Setup default mock return values for service queries
|
||||
mockTasksService.findAll.mockResolvedValue({
|
||||
data: [],
|
||||
meta: { total: 0, page: 1, limit: 50, totalPages: 0 },
|
||||
});
|
||||
mockEventsService.findAll.mockResolvedValue({
|
||||
data: [],
|
||||
meta: { total: 0, page: 1, limit: 50, totalPages: 0 },
|
||||
});
|
||||
mockProjectsService.findAll.mockResolvedValue({
|
||||
data: [],
|
||||
meta: { total: 0, page: 1, limit: 50, totalPages: 0 },
|
||||
});
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -500,4 +532,177 @@ describe("QueryService", () => {
|
||||
await expect(service.processQueryResponse(response)).rejects.toThrow("Invalid signature");
|
||||
});
|
||||
});
|
||||
|
||||
describe("query processing with actual data", () => {
|
||||
it("should process 'get tasks' query and return tasks data", async () => {
|
||||
const queryMessage = {
|
||||
messageId: "msg-1",
|
||||
instanceId: "remote-instance-1",
|
||||
query: "get tasks",
|
||||
context: { workspaceId: "workspace-1" },
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
|
||||
const mockConnection = {
|
||||
id: "connection-1",
|
||||
workspaceId: "workspace-1",
|
||||
remoteInstanceId: "remote-instance-1",
|
||||
status: FederationConnectionStatus.ACTIVE,
|
||||
};
|
||||
|
||||
const mockIdentity = {
|
||||
instanceId: "local-instance-1",
|
||||
};
|
||||
|
||||
mockPrisma.federationConnection.findFirst.mockResolvedValue(mockConnection);
|
||||
mockSignatureService.validateTimestamp.mockReturnValue(true);
|
||||
mockSignatureService.verifyMessage.mockResolvedValue({ valid: true });
|
||||
mockFederationService.getInstanceIdentity.mockResolvedValue(mockIdentity);
|
||||
mockSignatureService.signMessage.mockResolvedValue("response-signature");
|
||||
|
||||
const result = await service.handleIncomingQuery(queryMessage);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.correlationId).toBe(queryMessage.messageId);
|
||||
});
|
||||
|
||||
it("should process 'get events' query and return events data", async () => {
|
||||
const queryMessage = {
|
||||
messageId: "msg-2",
|
||||
instanceId: "remote-instance-1",
|
||||
query: "get events",
|
||||
context: { workspaceId: "workspace-1" },
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
|
||||
const mockConnection = {
|
||||
id: "connection-1",
|
||||
workspaceId: "workspace-1",
|
||||
remoteInstanceId: "remote-instance-1",
|
||||
status: FederationConnectionStatus.ACTIVE,
|
||||
};
|
||||
|
||||
const mockIdentity = {
|
||||
instanceId: "local-instance-1",
|
||||
};
|
||||
|
||||
mockPrisma.federationConnection.findFirst.mockResolvedValue(mockConnection);
|
||||
mockSignatureService.validateTimestamp.mockReturnValue(true);
|
||||
mockSignatureService.verifyMessage.mockResolvedValue({ valid: true });
|
||||
mockFederationService.getInstanceIdentity.mockResolvedValue(mockIdentity);
|
||||
mockSignatureService.signMessage.mockResolvedValue("response-signature");
|
||||
|
||||
const result = await service.handleIncomingQuery(queryMessage);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
});
|
||||
|
||||
it("should process 'get projects' query and return projects data", async () => {
|
||||
const queryMessage = {
|
||||
messageId: "msg-3",
|
||||
instanceId: "remote-instance-1",
|
||||
query: "get projects",
|
||||
context: { workspaceId: "workspace-1" },
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
|
||||
const mockConnection = {
|
||||
id: "connection-1",
|
||||
workspaceId: "workspace-1",
|
||||
remoteInstanceId: "remote-instance-1",
|
||||
status: FederationConnectionStatus.ACTIVE,
|
||||
};
|
||||
|
||||
const mockIdentity = {
|
||||
instanceId: "local-instance-1",
|
||||
};
|
||||
|
||||
mockPrisma.federationConnection.findFirst.mockResolvedValue(mockConnection);
|
||||
mockSignatureService.validateTimestamp.mockReturnValue(true);
|
||||
mockSignatureService.verifyMessage.mockResolvedValue({ valid: true });
|
||||
mockFederationService.getInstanceIdentity.mockResolvedValue(mockIdentity);
|
||||
mockSignatureService.signMessage.mockResolvedValue("response-signature");
|
||||
|
||||
const result = await service.handleIncomingQuery(queryMessage);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
});
|
||||
|
||||
it("should handle unknown query type gracefully", async () => {
|
||||
const queryMessage = {
|
||||
messageId: "msg-4",
|
||||
instanceId: "remote-instance-1",
|
||||
query: "unknown command",
|
||||
context: { workspaceId: "workspace-1" },
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
|
||||
const mockConnection = {
|
||||
id: "connection-1",
|
||||
workspaceId: "workspace-1",
|
||||
remoteInstanceId: "remote-instance-1",
|
||||
status: FederationConnectionStatus.ACTIVE,
|
||||
};
|
||||
|
||||
const mockIdentity = {
|
||||
instanceId: "local-instance-1",
|
||||
};
|
||||
|
||||
mockPrisma.federationConnection.findFirst.mockResolvedValue(mockConnection);
|
||||
mockSignatureService.validateTimestamp.mockReturnValue(true);
|
||||
mockSignatureService.verifyMessage.mockResolvedValue({ valid: true });
|
||||
mockFederationService.getInstanceIdentity.mockResolvedValue(mockIdentity);
|
||||
mockSignatureService.signMessage.mockResolvedValue("response-signature");
|
||||
|
||||
const result = await service.handleIncomingQuery(queryMessage);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Unknown query type");
|
||||
});
|
||||
|
||||
it("should enforce workspace context in queries", async () => {
|
||||
const queryMessage = {
|
||||
messageId: "msg-5",
|
||||
instanceId: "remote-instance-1",
|
||||
query: "get tasks",
|
||||
context: {}, // Missing workspaceId
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
|
||||
const mockConnection = {
|
||||
id: "connection-1",
|
||||
workspaceId: "workspace-1",
|
||||
remoteInstanceId: "remote-instance-1",
|
||||
status: FederationConnectionStatus.ACTIVE,
|
||||
};
|
||||
|
||||
const mockIdentity = {
|
||||
instanceId: "local-instance-1",
|
||||
};
|
||||
|
||||
mockPrisma.federationConnection.findFirst.mockResolvedValue(mockConnection);
|
||||
mockSignatureService.validateTimestamp.mockReturnValue(true);
|
||||
mockSignatureService.verifyMessage.mockResolvedValue({ valid: true });
|
||||
mockFederationService.getInstanceIdentity.mockResolvedValue(mockIdentity);
|
||||
mockSignatureService.signMessage.mockResolvedValue("response-signature");
|
||||
|
||||
const result = await service.handleIncomingQuery(queryMessage);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("workspaceId");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user