@aprovan/chat-backend 0.1.0-dev.4d82df8 → 0.1.0-dev.67fbce2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,133 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
2
- import { createChatApp } from "../../src/app";
3
- import { resetGatewaySessionCache } from "../../src/gateway-session";
4
-
5
- // ── Global fetch stub ─────────────────────────────────────────────────────────
6
-
7
- const mockFetch = vi.fn();
8
- vi.stubGlobal("fetch", mockFetch);
9
-
10
- // ── Middleware mocks ──────────────────────────────────────────────────────────
11
-
12
- vi.mock("aws-jwt-verify", () => {
13
- const verify = vi.fn().mockResolvedValue({
14
- sub: "user-123",
15
- exp: Math.floor(Date.now() / 1000) + 3600,
16
- token_use: "access",
17
- });
18
- return { CognitoJwtVerifier: { create: vi.fn(() => ({ verify })) } };
19
- });
20
-
21
- vi.mock("@aws-sdk/client-dynamodb", () => ({
22
- DynamoDBClient: vi.fn(() => ({})),
23
- }));
24
-
25
- const mockSend = vi.fn();
26
- vi.mock("@aws-sdk/lib-dynamodb", () => ({
27
- DynamoDBDocumentClient: { from: vi.fn(() => ({ send: mockSend })) },
28
- QueryCommand: vi.fn((i) => i),
29
- GetCommand: vi.fn((i) => i),
30
- }));
31
-
32
- // ── Environment + DDB setup ───────────────────────────────────────────────────
33
-
34
- beforeAll(() => {
35
- process.env["COGNITO_USER_POOL_ID"] = "us-east-1_test";
36
- process.env["COGNITO_CLIENT_ID"] = "test-client-id";
37
- process.env["AWS_REGION"] = "us-east-1";
38
- process.env["WORKSPACE_TABLE_NAME"] = "test-workspaces";
39
- process.env["MEMBERSHIPS_TABLE_NAME"] = "test-memberships";
40
- process.env["GATEWAY_URL"] = "https://gateway.test";
41
- process.env["OPENROUTER_API_KEY"] = "test-key";
42
- });
43
-
44
- beforeEach(() => {
45
- mockFetch.mockReset();
46
- resetGatewaySessionCache();
47
-
48
- mockSend.mockImplementation((cmd: { IndexName?: string }) => {
49
- if (cmd.IndexName === "ByUserSub") {
50
- return Promise.resolve({ Items: [{ workspaceId: "ws-test" }] });
51
- }
52
- return Promise.resolve({
53
- Item: {
54
- workspaceId: "ws-test",
55
- plan: "pro",
56
- limits: {},
57
- features: {},
58
- createdAt: "",
59
- updatedAt: "",
60
- },
61
- });
62
- });
63
- });
64
-
65
- afterEach(() => {
66
- vi.clearAllMocks();
67
- resetGatewaySessionCache();
68
- });
69
-
70
- // ── Tests ─────────────────────────────────────────────────────────────────────
71
-
72
- describe("POST /api/proxy/:ns/:proc", () => {
73
- it("forwards body and session token to the gateway and returns the response", async () => {
74
- const app = createChatApp();
75
- const expectedResult = { data: [{ id: "repo-1" }], meta: { requestId: "r1" } };
76
-
77
- mockFetch
78
- // POST /auth/sessions
79
- .mockResolvedValueOnce(new Response(JSON.stringify({ workspace_id: "ws-test" }), { status: 200 }))
80
- // POST /tools/github/repos.list
81
- .mockResolvedValueOnce(new Response(JSON.stringify(expectedResult), { status: 200 }));
82
-
83
- const res = await app.request("/api/proxy/github/repos.list", {
84
- method: "POST",
85
- headers: {
86
- "Content-Type": "application/json",
87
- Authorization: "Bearer cognito-token",
88
- },
89
- body: JSON.stringify({ per_page: 10 }),
90
- });
91
-
92
- expect(res.status).toBe(200);
93
- const body = await res.json();
94
- expect(body).toEqual(expectedResult);
95
-
96
- // Verify the gateway call used the session bearer and forwarded the body
97
- const toolCall = mockFetch.mock.calls.find(
98
- (c) => typeof c[0] === "string" && (c[0] as string).includes("/tools/github/repos.list"),
99
- );
100
- expect(toolCall).toBeDefined();
101
- const toolCallOptions = toolCall![1] as RequestInit;
102
- expect((toolCallOptions.headers as Record<string, string>)["Authorization"]).toBe(
103
- "Bearer cognito-token",
104
- );
105
- expect(JSON.parse(toolCallOptions.body as string)).toEqual({ per_page: 10 });
106
- });
107
-
108
- it("returns 401 without Authorization header", async () => {
109
- const app = createChatApp();
110
- const res = await app.request("/api/proxy/github/repos.list", { method: "POST" });
111
- expect(res.status).toBe(401);
112
- });
113
-
114
- it("forwards the gateway error status when gateway returns non-2xx", async () => {
115
- const app = createChatApp();
116
-
117
- mockFetch
118
- .mockResolvedValueOnce(new Response(JSON.stringify({ workspace_id: "ws-test" }), { status: 200 }))
119
- .mockResolvedValueOnce(
120
- new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 }),
121
- );
122
-
123
- const res = await app.request("/api/proxy/github/repos.list", {
124
- method: "POST",
125
- headers: {
126
- "Content-Type": "application/json",
127
- Authorization: "Bearer cognito-token",
128
- },
129
- });
130
-
131
- expect(res.status).toBe(403);
132
- });
133
- });
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
2
- import { createChatApp } from "../../src/app";
3
- import { resetGatewaySessionCache } from "../../src/gateway-session";
4
-
5
- // ── Global fetch stub ─────────────────────────────────────────────────────────
6
-
7
- const mockFetch = vi.fn();
8
- vi.stubGlobal("fetch", mockFetch);
9
-
10
- // ── Middleware mocks ──────────────────────────────────────────────────────────
11
-
12
- vi.mock("aws-jwt-verify", () => {
13
- const verify = vi.fn().mockResolvedValue({
14
- sub: "user-123",
15
- exp: Math.floor(Date.now() / 1000) + 3600,
16
- token_use: "access",
17
- });
18
- return { CognitoJwtVerifier: { create: vi.fn(() => ({ verify })) } };
19
- });
20
-
21
- vi.mock("@aws-sdk/client-dynamodb", () => ({
22
- DynamoDBClient: vi.fn(() => ({})),
23
- }));
24
-
25
- const mockSend = vi.fn();
26
- vi.mock("@aws-sdk/lib-dynamodb", () => ({
27
- DynamoDBDocumentClient: { from: vi.fn(() => ({ send: mockSend })) },
28
- QueryCommand: vi.fn((i) => i),
29
- GetCommand: vi.fn((i) => i),
30
- }));
31
-
32
- // ── Environment + DDB setup ───────────────────────────────────────────────────
33
-
34
- beforeAll(() => {
35
- process.env["COGNITO_USER_POOL_ID"] = "us-east-1_test";
36
- process.env["COGNITO_CLIENT_ID"] = "test-client-id";
37
- process.env["AWS_REGION"] = "us-east-1";
38
- process.env["WORKSPACE_TABLE_NAME"] = "test-workspaces";
39
- process.env["MEMBERSHIPS_TABLE_NAME"] = "test-memberships";
40
- process.env["GATEWAY_URL"] = "https://gateway.test";
41
- process.env["OPENROUTER_API_KEY"] = "test-key";
42
- });
43
-
44
- beforeEach(() => {
45
- mockFetch.mockReset();
46
- resetGatewaySessionCache();
47
-
48
- mockSend.mockImplementation((cmd: { IndexName?: string }) => {
49
- if (cmd.IndexName === "ByUserSub") {
50
- return Promise.resolve({ Items: [{ workspaceId: "ws-test" }] });
51
- }
52
- return Promise.resolve({
53
- Item: {
54
- workspaceId: "ws-test",
55
- plan: "pro",
56
- limits: {},
57
- features: {},
58
- createdAt: "",
59
- updatedAt: "",
60
- },
61
- });
62
- });
63
- });
64
-
65
- afterEach(() => {
66
- vi.clearAllMocks();
67
- resetGatewaySessionCache();
68
- });
69
-
70
- // ── Tests ─────────────────────────────────────────────────────────────────────
71
-
72
- describe("GET /api/services", () => {
73
- it("returns namespaces and services from the gateway", async () => {
74
- const app = createChatApp();
75
-
76
- mockFetch
77
- // POST /auth/sessions
78
- .mockResolvedValueOnce(new Response(JSON.stringify({ workspace_id: "ws-test" }), { status: 200 }))
79
- // GET /tools
80
- .mockResolvedValueOnce(
81
- new Response(
82
- JSON.stringify({
83
- tools: [
84
- {
85
- provider: "github",
86
- name: "github.repos_list",
87
- operation: "repos_list",
88
- description: "List repos",
89
- inputSchema: { type: "object", properties: { per_page: { type: "number" } } },
90
- },
91
- ],
92
- workspace_id: "ws-test",
93
- }),
94
- { status: 200 },
95
- ),
96
- );
97
-
98
- const res = await app.request("/api/services", {
99
- headers: { Authorization: "Bearer test-token" },
100
- });
101
-
102
- expect(res.status).toBe(200);
103
- const body = await res.json() as { namespaces: string[]; services: unknown[] };
104
- expect(body.namespaces).toContain("github");
105
- expect(body.services).toHaveLength(1);
106
- expect((body.services[0] as { namespace: string }).namespace).toBe("github");
107
- });
108
-
109
- it("returns 502 when gateway tools fetch fails", async () => {
110
- const app = createChatApp();
111
-
112
- mockFetch
113
- .mockResolvedValueOnce(new Response(JSON.stringify({ workspace_id: "ws-test" }), { status: 200 }))
114
- .mockResolvedValueOnce(new Response("Internal error", { status: 500 }));
115
-
116
- const res = await app.request("/api/services", {
117
- headers: { Authorization: "Bearer test-token" },
118
- });
119
-
120
- expect(res.status).toBe(502);
121
- });
122
-
123
- it("returns 401 without Authorization header", async () => {
124
- const app = createChatApp();
125
- const res = await app.request("/api/services");
126
- expect(res.status).toBe(401);
127
- });
128
- });