@esaio/esa-mcp-server 0.2.0 → 0.2.2
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.
- package/README.en.md +10 -6
- package/README.md +16 -12
- package/bin/{index.js → index.mjs} +1 -1
- package/package.json +22 -3
- package/.dockerignore +0 -36
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/docker-publish.yml +0 -120
- package/.github/workflows/main.yml +0 -41
- package/CLAUDE.md +0 -94
- package/Dockerfile +0 -34
- package/biome.json +0 -57
- package/src/__tests__/fixtures/mock-comment.ts +0 -90
- package/src/__tests__/fixtures/mock-post.ts +0 -79
- package/src/__tests__/index.test.ts +0 -216
- package/src/api_client/__tests__/index.test.ts +0 -149
- package/src/api_client/__tests__/middleware.test.ts +0 -120
- package/src/api_client/__tests__/with-context.test.ts +0 -98
- package/src/api_client/index.ts +0 -29
- package/src/api_client/middleware.ts +0 -21
- package/src/api_client/with-context.ts +0 -26
- package/src/config/__tests__/index.test.ts +0 -65
- package/src/config/index.ts +0 -20
- package/src/context/mcp-context.ts +0 -1
- package/src/context/stdio-context.ts +0 -6
- package/src/errors/missing-team-name-error.ts +0 -8
- package/src/formatters/__tests__/mcp-response.test.ts +0 -106
- package/src/formatters/mcp-response.ts +0 -95
- package/src/generated/api-types.ts +0 -2968
- package/src/i18n/__tests__/index.test.ts +0 -53
- package/src/i18n/index.ts +0 -39
- package/src/index.ts +0 -47
- package/src/locales/en.json +0 -13
- package/src/locales/ja.json +0 -13
- package/src/prompts/__tests__/index.test.ts +0 -48
- package/src/prompts/__tests__/summarize-post.test.ts +0 -291
- package/src/prompts/index.ts +0 -21
- package/src/prompts/summarize-post.ts +0 -94
- package/src/resources/__tests__/index.test.ts +0 -50
- package/src/resources/__tests__/recent-posts-list.test.ts +0 -92
- package/src/resources/__tests__/recent-posts.test.ts +0 -270
- package/src/resources/index.ts +0 -33
- package/src/resources/recent-posts-list.ts +0 -22
- package/src/resources/recent-posts.ts +0 -45
- package/src/schemas/team-name-schema.ts +0 -19
- package/src/tools/__tests__/attachments.test.ts +0 -460
- package/src/tools/__tests__/categories.test.ts +0 -402
- package/src/tools/__tests__/comments.test.ts +0 -970
- package/src/tools/__tests__/helps.test.ts +0 -222
- package/src/tools/__tests__/index.test.ts +0 -48
- package/src/tools/__tests__/post-actions.test.ts +0 -445
- package/src/tools/__tests__/posts.test.ts +0 -917
- package/src/tools/__tests__/search.test.ts +0 -339
- package/src/tools/__tests__/teams.test.ts +0 -615
- package/src/tools/attachments.ts +0 -167
- package/src/tools/categories.ts +0 -153
- package/src/tools/comments.ts +0 -258
- package/src/tools/helps.ts +0 -50
- package/src/tools/index.ts +0 -351
- package/src/tools/post-actions.ts +0 -132
- package/src/tools/posts.ts +0 -179
- package/src/tools/search.ts +0 -98
- package/src/tools/teams.ts +0 -157
- package/src/transformers/__tests__/category-transformer.test.ts +0 -161
- package/src/transformers/__tests__/comment-transformer.test.ts +0 -129
- package/src/transformers/__tests__/post-name-normalizer.test.ts +0 -53
- package/src/transformers/__tests__/post-transformer.test.ts +0 -70
- package/src/transformers/__tests__/query-normalizer.test.ts +0 -98
- package/src/transformers/__tests__/team-name-normalizer.test.ts +0 -21
- package/src/transformers/category-transformer.ts +0 -36
- package/src/transformers/comment-transformer.ts +0 -34
- package/src/transformers/post-name-normalizer.ts +0 -30
- package/src/transformers/post-transformer.ts +0 -38
- package/src/transformers/query-normalizer.ts +0 -36
- package/src/transformers/team-name-normalizer.ts +0 -7
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -30
- package/tsdown.config.ts +0 -13
- package/vitest.config.ts +0 -24
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { components } from "../../generated/api-types.js";
|
|
2
|
-
import { transformComment } from "../../transformers/comment-transformer.js";
|
|
3
|
-
|
|
4
|
-
export function createMockComment(
|
|
5
|
-
overrides?: Partial<components["schemas"]["Comment"]>,
|
|
6
|
-
): components["schemas"]["Comment"] {
|
|
7
|
-
return {
|
|
8
|
-
id: 123,
|
|
9
|
-
post_number: 456,
|
|
10
|
-
body_md: "This is a test comment content",
|
|
11
|
-
body_html: "<p>This is a test comment content</p>",
|
|
12
|
-
created_at: "2024-01-01T00:00:00+09:00",
|
|
13
|
-
updated_at: "2024-01-01T01:00:00+09:00",
|
|
14
|
-
url: "https://test-team.esa.example.com/posts/456#comment-123",
|
|
15
|
-
created_by: {
|
|
16
|
-
name: "Test User",
|
|
17
|
-
screen_name: "testuser",
|
|
18
|
-
icon: "https://example.com/icon.png",
|
|
19
|
-
myself: true,
|
|
20
|
-
},
|
|
21
|
-
stargazers_count: 5,
|
|
22
|
-
star: true,
|
|
23
|
-
stargazers: [
|
|
24
|
-
{
|
|
25
|
-
created_at: "2024-01-01T02:00:00+09:00",
|
|
26
|
-
body: "Great comment!",
|
|
27
|
-
user: {
|
|
28
|
-
name: "Stargazer User",
|
|
29
|
-
screen_name: "stargazer",
|
|
30
|
-
icon: "https://example.com/star-icon.png",
|
|
31
|
-
myself: false,
|
|
32
|
-
},
|
|
33
|
-
name: "Stargazer User",
|
|
34
|
-
screen_name: "stargazer",
|
|
35
|
-
icon: "https://example.com/star-icon.png",
|
|
36
|
-
myself: false,
|
|
37
|
-
email: "stargazer@example.com",
|
|
38
|
-
role: "member" as const,
|
|
39
|
-
posts_count: 10,
|
|
40
|
-
joined_at: "2024-01-01T00:00:00+09:00",
|
|
41
|
-
last_accessed_at: "2024-01-01T02:00:00+09:00",
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
...overrides,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function createLongContentComment(
|
|
49
|
-
length: number,
|
|
50
|
-
): components["schemas"]["Comment"] {
|
|
51
|
-
const longContent = "a".repeat(length);
|
|
52
|
-
return createMockComment({
|
|
53
|
-
body_md: longContent,
|
|
54
|
-
body_html: `<p>${longContent}</p>`,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function createNullBodyComment(
|
|
59
|
-
overrides?: Partial<components["schemas"]["Comment"]>,
|
|
60
|
-
): components["schemas"]["Comment"] {
|
|
61
|
-
return createMockComment({
|
|
62
|
-
body_md: "",
|
|
63
|
-
body_html: "",
|
|
64
|
-
...overrides,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function createExpectedTransformedComment(
|
|
69
|
-
comment: components["schemas"]["Comment"],
|
|
70
|
-
) {
|
|
71
|
-
return transformComment(comment);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function createMockCommentList(
|
|
75
|
-
overrides?: Partial<components["schemas"]["CommentList"]>,
|
|
76
|
-
): components["schemas"]["CommentList"] {
|
|
77
|
-
return {
|
|
78
|
-
comments: [
|
|
79
|
-
createMockComment({ id: 1, post_number: 123, body_md: "First comment" }),
|
|
80
|
-
createMockComment({ id: 2, post_number: 124, body_md: "Second comment" }),
|
|
81
|
-
],
|
|
82
|
-
prev_page: null,
|
|
83
|
-
next_page: 2,
|
|
84
|
-
total_count: 10,
|
|
85
|
-
page: 1,
|
|
86
|
-
per_page: 20,
|
|
87
|
-
max_per_page: 100,
|
|
88
|
-
...overrides,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { components } from "../../generated/api-types.js";
|
|
2
|
-
import {
|
|
3
|
-
type PostTransformOptions,
|
|
4
|
-
transformPost,
|
|
5
|
-
} from "../../transformers/post-transformer.js";
|
|
6
|
-
|
|
7
|
-
export const createMockPost = (
|
|
8
|
-
overrides: Partial<components["schemas"]["Post"]> = {},
|
|
9
|
-
): components["schemas"]["Post"] => ({
|
|
10
|
-
number: 123,
|
|
11
|
-
name: "test-post.md",
|
|
12
|
-
tags: ["tag1", "tag2"],
|
|
13
|
-
category: "dev",
|
|
14
|
-
full_name: "dev/test-post.md #tag1 #tag2",
|
|
15
|
-
wip: false,
|
|
16
|
-
body_md: "# Test Post\n\nThis is a test post content.",
|
|
17
|
-
body_html: "<h1>Test Post</h1><p>This is a test post content.</p>",
|
|
18
|
-
created_at: "2024-01-01T00:00:00+09:00",
|
|
19
|
-
updated_at: "2024-01-02T00:00:00+09:00",
|
|
20
|
-
message: "Update test post",
|
|
21
|
-
url: "https://test-team.esa.example.com/posts/123",
|
|
22
|
-
revision_number: 3,
|
|
23
|
-
created_by: {
|
|
24
|
-
name: "user1",
|
|
25
|
-
screen_name: "user1",
|
|
26
|
-
icon: "https://example.com/icon1.png",
|
|
27
|
-
myself: false,
|
|
28
|
-
},
|
|
29
|
-
updated_by: {
|
|
30
|
-
name: "user2",
|
|
31
|
-
screen_name: "user2",
|
|
32
|
-
icon: "https://example.com/icon2.png",
|
|
33
|
-
myself: false,
|
|
34
|
-
},
|
|
35
|
-
kind: "stock",
|
|
36
|
-
comments_count: 5,
|
|
37
|
-
tasks_count: 3,
|
|
38
|
-
done_tasks_count: 2,
|
|
39
|
-
stargazers_count: 10,
|
|
40
|
-
watchers_count: 8,
|
|
41
|
-
star: true,
|
|
42
|
-
watch: false,
|
|
43
|
-
...overrides,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
export const createWipPost = (
|
|
47
|
-
overrides: Partial<components["schemas"]["Post"]> = {},
|
|
48
|
-
): components["schemas"]["Post"] =>
|
|
49
|
-
createMockPost({
|
|
50
|
-
wip: true,
|
|
51
|
-
number: 456,
|
|
52
|
-
name: "wip-post.md",
|
|
53
|
-
full_name: "docs/wip-post.md #wip",
|
|
54
|
-
url: "https://test-team.esa.example.com/posts/456",
|
|
55
|
-
kind: "flow",
|
|
56
|
-
...overrides,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export const createLongContentPost = (
|
|
60
|
-
contentLength = 600,
|
|
61
|
-
): components["schemas"]["Post"] =>
|
|
62
|
-
createMockPost({
|
|
63
|
-
body_md: "a".repeat(contentLength),
|
|
64
|
-
body_html: `<p>${"a".repeat(contentLength)}</p>`,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export const createNullBodyPost = (
|
|
68
|
-
overrides: Partial<components["schemas"]["Post"]> = {},
|
|
69
|
-
): components["schemas"]["Post"] =>
|
|
70
|
-
createMockPost({
|
|
71
|
-
body_md: undefined,
|
|
72
|
-
body_html: undefined,
|
|
73
|
-
...overrides,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
export const createExpectedTransformed = (
|
|
77
|
-
post: components["schemas"]["Post"],
|
|
78
|
-
options: PostTransformOptions = {},
|
|
79
|
-
) => transformPost(post, options);
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import type { MockInstance } from "vitest";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
|
|
6
|
-
describe("MCP Server", () => {
|
|
7
|
-
let consoleErrorSpy: MockInstance<typeof console.error>;
|
|
8
|
-
let processExitSpy: MockInstance<typeof process.exit>;
|
|
9
|
-
|
|
10
|
-
const createMockConfig = (behavior?: { shouldThrow?: boolean }) => ({
|
|
11
|
-
config: {
|
|
12
|
-
server: {
|
|
13
|
-
name: "test-server",
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
},
|
|
16
|
-
esa: {
|
|
17
|
-
apiAccessToken: "test-token",
|
|
18
|
-
apiBaseUrl: "https://api.esa.example.com",
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
validateConfig: vi.fn().mockImplementation(() => {
|
|
22
|
-
if (behavior?.shouldThrow) {
|
|
23
|
-
throw new Error("Invalid config");
|
|
24
|
-
}
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const createMockServer = (behavior?: { shouldFailConnect?: boolean }) => {
|
|
29
|
-
const mockConnect = behavior?.shouldFailConnect
|
|
30
|
-
? vi.fn().mockRejectedValue(new Error("Connection failed"))
|
|
31
|
-
: vi.fn().mockResolvedValue(undefined);
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
connect: mockConnect,
|
|
35
|
-
} as unknown as McpServer;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const createMockTransport = () =>
|
|
39
|
-
({
|
|
40
|
-
onclose: undefined as (() => void) | undefined,
|
|
41
|
-
onerror: undefined as ((error: Error) => void) | undefined,
|
|
42
|
-
}) as unknown as StdioServerTransport;
|
|
43
|
-
|
|
44
|
-
const setupServerMocks = (
|
|
45
|
-
mockConfig: ReturnType<typeof createMockConfig>,
|
|
46
|
-
mockServer: ReturnType<typeof createMockServer>,
|
|
47
|
-
mockTransport: ReturnType<typeof createMockTransport>,
|
|
48
|
-
setupFunctions?: {
|
|
49
|
-
setupTools?: ReturnType<typeof vi.fn>;
|
|
50
|
-
setupResources?: ReturnType<typeof vi.fn>;
|
|
51
|
-
setupPrompts?: ReturnType<typeof vi.fn>;
|
|
52
|
-
initI18n?: ReturnType<typeof vi.fn>;
|
|
53
|
-
},
|
|
54
|
-
) => {
|
|
55
|
-
// Use function keyword for constructor mocks (required in vitest v4)
|
|
56
|
-
// biome-ignore lint/complexity/useArrowFunction: Constructor mocks in vitest v4 require function keyword, not arrow functions
|
|
57
|
-
const MockMcpServer = vi.fn(function (..._args: unknown[]) {
|
|
58
|
-
return mockServer;
|
|
59
|
-
});
|
|
60
|
-
// biome-ignore lint/complexity/useArrowFunction: Constructor mocks in vitest v4 require function keyword, not arrow functions
|
|
61
|
-
const MockStdioServerTransport = vi.fn(function (..._args: unknown[]) {
|
|
62
|
-
return mockTransport;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
vi.doMock("../config/index.js", () => mockConfig);
|
|
66
|
-
vi.doMock("@modelcontextprotocol/sdk/server/mcp.js", () => ({
|
|
67
|
-
McpServer: MockMcpServer,
|
|
68
|
-
}));
|
|
69
|
-
vi.doMock("@modelcontextprotocol/sdk/server/stdio.js", () => ({
|
|
70
|
-
StdioServerTransport: MockStdioServerTransport,
|
|
71
|
-
}));
|
|
72
|
-
vi.doMock("../tools/index.js", () => ({
|
|
73
|
-
setupTools: setupFunctions?.setupTools ?? vi.fn(),
|
|
74
|
-
}));
|
|
75
|
-
vi.doMock("../resources/index.js", () => ({
|
|
76
|
-
setupResources: setupFunctions?.setupResources ?? vi.fn(),
|
|
77
|
-
}));
|
|
78
|
-
vi.doMock("../prompts/index.js", () => ({
|
|
79
|
-
setupPrompts: setupFunctions?.setupPrompts ?? vi.fn(),
|
|
80
|
-
}));
|
|
81
|
-
vi.doMock("../i18n/index.js", () => ({
|
|
82
|
-
initI18n:
|
|
83
|
-
setupFunctions?.initI18n ?? vi.fn().mockResolvedValue(undefined),
|
|
84
|
-
}));
|
|
85
|
-
|
|
86
|
-
return { MockMcpServer, MockStdioServerTransport };
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
vi.resetModules();
|
|
91
|
-
vi.clearAllMocks();
|
|
92
|
-
|
|
93
|
-
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
94
|
-
processExitSpy = vi
|
|
95
|
-
.spyOn(process, "exit")
|
|
96
|
-
.mockImplementation(() => undefined as never);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
afterEach(() => {
|
|
100
|
-
consoleErrorSpy.mockRestore();
|
|
101
|
-
processExitSpy.mockRestore();
|
|
102
|
-
vi.resetModules();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("should validate config on startup", async () => {
|
|
106
|
-
const mockConfig = createMockConfig();
|
|
107
|
-
const mockServer = createMockServer();
|
|
108
|
-
const mockTransport = createMockTransport();
|
|
109
|
-
|
|
110
|
-
setupServerMocks(mockConfig, mockServer, mockTransport);
|
|
111
|
-
|
|
112
|
-
await import("../index.js");
|
|
113
|
-
|
|
114
|
-
expect(mockConfig.validateConfig).toHaveBeenCalled();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("should exit with error when config validation fails", async () => {
|
|
118
|
-
const mockConfig = createMockConfig({ shouldThrow: true });
|
|
119
|
-
|
|
120
|
-
vi.doMock("../config/index.js", () => mockConfig);
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
await import("../index.js");
|
|
124
|
-
} catch (_error) {
|
|
125
|
-
// Expected to throw due to config validation
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
129
|
-
"Configuration error:",
|
|
130
|
-
expect.any(Error),
|
|
131
|
-
);
|
|
132
|
-
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("should create MCP server and connect", async () => {
|
|
136
|
-
const mockConfig = createMockConfig();
|
|
137
|
-
const mockServer = createMockServer();
|
|
138
|
-
const mockTransport = createMockTransport();
|
|
139
|
-
|
|
140
|
-
// Track setup function calls
|
|
141
|
-
const mockSetupTools = vi.fn();
|
|
142
|
-
const mockSetupResources = vi.fn();
|
|
143
|
-
const mockSetupPrompts = vi.fn();
|
|
144
|
-
|
|
145
|
-
const { MockMcpServer } = setupServerMocks(
|
|
146
|
-
mockConfig,
|
|
147
|
-
mockServer,
|
|
148
|
-
mockTransport,
|
|
149
|
-
{
|
|
150
|
-
setupTools: mockSetupTools,
|
|
151
|
-
setupResources: mockSetupResources,
|
|
152
|
-
setupPrompts: mockSetupPrompts,
|
|
153
|
-
},
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
await import("../index.js");
|
|
157
|
-
|
|
158
|
-
expect(MockMcpServer).toHaveBeenCalledWith({
|
|
159
|
-
name: "test-server",
|
|
160
|
-
version: "1.0.0",
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const expectedEsaConfig = {
|
|
164
|
-
apiAccessToken: "test-token",
|
|
165
|
-
apiBaseUrl: "https://api.esa.example.com",
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
expect(mockSetupTools).toHaveBeenCalledWith(mockServer, expectedEsaConfig);
|
|
169
|
-
expect(mockSetupResources).toHaveBeenCalledWith(
|
|
170
|
-
mockServer,
|
|
171
|
-
expectedEsaConfig,
|
|
172
|
-
);
|
|
173
|
-
expect(mockSetupPrompts).toHaveBeenCalledWith(
|
|
174
|
-
mockServer,
|
|
175
|
-
expectedEsaConfig,
|
|
176
|
-
);
|
|
177
|
-
expect(mockServer.connect).toHaveBeenCalled();
|
|
178
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith("test-server v1.0.0 started");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("should set up transport error handlers", async () => {
|
|
182
|
-
const mockConfig = createMockConfig();
|
|
183
|
-
const mockServer = createMockServer();
|
|
184
|
-
const mockTransport = createMockTransport();
|
|
185
|
-
|
|
186
|
-
setupServerMocks(mockConfig, mockServer, mockTransport);
|
|
187
|
-
|
|
188
|
-
await import("../index.js");
|
|
189
|
-
|
|
190
|
-
expect(mockTransport.onclose).toBeDefined();
|
|
191
|
-
expect(mockTransport.onerror).toBeDefined();
|
|
192
|
-
|
|
193
|
-
mockTransport.onclose?.();
|
|
194
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith("Transport closed");
|
|
195
|
-
|
|
196
|
-
const testError = new Error("Test error");
|
|
197
|
-
mockTransport.onerror?.(testError);
|
|
198
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith("Transport error:", testError);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("should handle server startup errors", async () => {
|
|
202
|
-
const mockConfig = createMockConfig();
|
|
203
|
-
const mockServer = createMockServer({ shouldFailConnect: true });
|
|
204
|
-
const mockTransport = createMockTransport();
|
|
205
|
-
|
|
206
|
-
setupServerMocks(mockConfig, mockServer, mockTransport);
|
|
207
|
-
|
|
208
|
-
await import("../index.js");
|
|
209
|
-
|
|
210
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
211
|
-
"Server startup error:",
|
|
212
|
-
expect.any(Error),
|
|
213
|
-
);
|
|
214
|
-
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import createClient from "openapi-fetch";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import packageJson from "../../../package.json" with { type: "json" };
|
|
4
|
-
import type { paths } from "../../generated/api-types.js";
|
|
5
|
-
import { createEsaClient } from "../index.js";
|
|
6
|
-
|
|
7
|
-
// Mock openapi-fetch to test client configuration without actual HTTP calls
|
|
8
|
-
vi.mock("openapi-fetch", () => ({
|
|
9
|
-
default: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
describe("createEsaClient", () => {
|
|
13
|
-
// Create mock client with use method to verify middleware registration
|
|
14
|
-
const createMockClient = () =>
|
|
15
|
-
({
|
|
16
|
-
use: vi.fn(),
|
|
17
|
-
}) as unknown as ReturnType<typeof createClient<paths>>;
|
|
18
|
-
|
|
19
|
-
// Setup test environment with configurable parameters for different test scenarios
|
|
20
|
-
const setupTest = (overrides?: {
|
|
21
|
-
apiBaseUrl?: string;
|
|
22
|
-
apiAccessToken?: string;
|
|
23
|
-
}) => {
|
|
24
|
-
const config = {
|
|
25
|
-
apiBaseUrl: overrides?.apiBaseUrl ?? "https://api.esa.example.com",
|
|
26
|
-
apiAccessToken: overrides?.apiAccessToken ?? "test-token",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const mockClient = createMockClient();
|
|
30
|
-
const mockCreateClient = vi.mocked(createClient);
|
|
31
|
-
mockCreateClient.mockReturnValue(mockClient);
|
|
32
|
-
|
|
33
|
-
return { config, mockClient, mockCreateClient };
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
vi.clearAllMocks();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should create client with correct baseUrl", () => {
|
|
41
|
-
const { config, mockCreateClient } = setupTest();
|
|
42
|
-
|
|
43
|
-
createEsaClient(config.apiAccessToken, config.apiBaseUrl);
|
|
44
|
-
|
|
45
|
-
expect(mockCreateClient).toHaveBeenCalledWith({
|
|
46
|
-
baseUrl: config.apiBaseUrl,
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should add User-Agent and Auth middleware to client", () => {
|
|
51
|
-
const { config, mockClient } = setupTest();
|
|
52
|
-
|
|
53
|
-
createEsaClient(config.apiAccessToken, config.apiBaseUrl);
|
|
54
|
-
|
|
55
|
-
expect(mockClient.use).toHaveBeenCalledTimes(2);
|
|
56
|
-
expect(mockClient.use).toHaveBeenNthCalledWith(
|
|
57
|
-
1,
|
|
58
|
-
expect.objectContaining({
|
|
59
|
-
onRequest: expect.any(Function),
|
|
60
|
-
}),
|
|
61
|
-
);
|
|
62
|
-
expect(mockClient.use).toHaveBeenNthCalledWith(
|
|
63
|
-
2,
|
|
64
|
-
expect.objectContaining({
|
|
65
|
-
onRequest: expect.any(Function),
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("should return the configured client", () => {
|
|
71
|
-
const { config, mockClient } = setupTest();
|
|
72
|
-
|
|
73
|
-
const result = createEsaClient(config.apiAccessToken, config.apiBaseUrl);
|
|
74
|
-
|
|
75
|
-
expect(result).toBe(mockClient);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("should set User-Agent header in middleware", async () => {
|
|
79
|
-
const { config, mockClient } = setupTest();
|
|
80
|
-
|
|
81
|
-
createEsaClient(config.apiAccessToken, config.apiBaseUrl);
|
|
82
|
-
|
|
83
|
-
// Get the User-Agent middleware function (first one registered)
|
|
84
|
-
const useMock = mockClient.use as ReturnType<typeof vi.fn>;
|
|
85
|
-
const userAgentMiddleware = useMock.mock.calls[0][0];
|
|
86
|
-
|
|
87
|
-
// Create mock request to verify User-Agent header is set
|
|
88
|
-
const mockRequest = {
|
|
89
|
-
headers: {
|
|
90
|
-
set: vi.fn(),
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
await userAgentMiddleware.onRequest({ request: mockRequest });
|
|
95
|
-
|
|
96
|
-
expect(mockRequest.headers.set).toHaveBeenCalledWith(
|
|
97
|
-
"User-Agent",
|
|
98
|
-
`esa-mcp-server/${packageJson.version} (official)`,
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("should set Authorization header in middleware", async () => {
|
|
103
|
-
const { config, mockClient } = setupTest();
|
|
104
|
-
|
|
105
|
-
createEsaClient(config.apiAccessToken, config.apiBaseUrl);
|
|
106
|
-
|
|
107
|
-
// Get the Auth middleware function (second one registered)
|
|
108
|
-
const useMock = mockClient.use as ReturnType<typeof vi.fn>;
|
|
109
|
-
const authMiddleware = useMock.mock.calls[1][0];
|
|
110
|
-
|
|
111
|
-
// Create mock request to verify Authorization header is set
|
|
112
|
-
const mockRequest = {
|
|
113
|
-
headers: {
|
|
114
|
-
set: vi.fn(),
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
await authMiddleware.onRequest({ request: mockRequest });
|
|
119
|
-
|
|
120
|
-
expect(mockRequest.headers.set).toHaveBeenCalledWith(
|
|
121
|
-
"Authorization",
|
|
122
|
-
`Bearer ${config.apiAccessToken}`,
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should create separate client instances for different tokens", () => {
|
|
127
|
-
const { config, mockCreateClient, mockClient } = setupTest();
|
|
128
|
-
const token1 = "token-1";
|
|
129
|
-
const token2 = "token-2";
|
|
130
|
-
|
|
131
|
-
createEsaClient(token1, config.apiBaseUrl);
|
|
132
|
-
createEsaClient(token2, config.apiBaseUrl);
|
|
133
|
-
|
|
134
|
-
expect(mockCreateClient).toHaveBeenCalledTimes(2);
|
|
135
|
-
expect(mockClient.use).toHaveBeenCalledTimes(4); // 2 middlewares × 2 clients
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("should create separate client instances for different base URLs", () => {
|
|
139
|
-
const { config, mockCreateClient } = setupTest();
|
|
140
|
-
const url1 = "https://api.esa.example.com";
|
|
141
|
-
const url2 = "https://api.example.com";
|
|
142
|
-
|
|
143
|
-
createEsaClient(config.apiAccessToken, url1);
|
|
144
|
-
createEsaClient(config.apiAccessToken, url2);
|
|
145
|
-
|
|
146
|
-
expect(mockCreateClient).toHaveBeenCalledWith({ baseUrl: url1 });
|
|
147
|
-
expect(mockCreateClient).toHaveBeenCalledWith({ baseUrl: url2 });
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { MockInstance } from "vitest";
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { createAuthMiddleware } from "../middleware.js";
|
|
4
|
-
|
|
5
|
-
describe("createAuthMiddleware", () => {
|
|
6
|
-
// Mock console.error to verify rate limit and error logging without console output
|
|
7
|
-
let consoleErrorSpy: MockInstance<typeof console.error>;
|
|
8
|
-
|
|
9
|
-
// Create test request for authorization header testing
|
|
10
|
-
const createTestRequest = (url = "https://api.esa.example.com/test") =>
|
|
11
|
-
new Request(url);
|
|
12
|
-
|
|
13
|
-
// Create test response with optional rate limit headers
|
|
14
|
-
const createTestResponse = (rateLimitHeaders?: {
|
|
15
|
-
limit?: string;
|
|
16
|
-
remaining?: string;
|
|
17
|
-
}) => {
|
|
18
|
-
const headers: Record<string, string> = {};
|
|
19
|
-
if (rateLimitHeaders?.limit) {
|
|
20
|
-
headers["x-ratelimit-limit"] = rateLimitHeaders.limit;
|
|
21
|
-
}
|
|
22
|
-
if (rateLimitHeaders?.remaining) {
|
|
23
|
-
headers["x-ratelimit-remaining"] = rateLimitHeaders.remaining;
|
|
24
|
-
}
|
|
25
|
-
return new Response(null, { headers });
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Helper to call middleware methods with minimal required context using as unknown as pattern
|
|
29
|
-
const callOnRequest = (
|
|
30
|
-
middleware: ReturnType<typeof createAuthMiddleware>,
|
|
31
|
-
request: Request,
|
|
32
|
-
) =>
|
|
33
|
-
middleware.onRequest?.({
|
|
34
|
-
request,
|
|
35
|
-
schemaPath: "/test",
|
|
36
|
-
params: {},
|
|
37
|
-
id: "test-id",
|
|
38
|
-
options: { baseUrl: "https://api.esa.example.com" },
|
|
39
|
-
} as unknown as Parameters<NonNullable<typeof middleware.onRequest>>[0]);
|
|
40
|
-
|
|
41
|
-
const callOnResponse = (
|
|
42
|
-
middleware: ReturnType<typeof createAuthMiddleware>,
|
|
43
|
-
response: Response,
|
|
44
|
-
) =>
|
|
45
|
-
middleware.onResponse?.({
|
|
46
|
-
request: createTestRequest(),
|
|
47
|
-
response,
|
|
48
|
-
schemaPath: "/test",
|
|
49
|
-
params: {},
|
|
50
|
-
id: "test-id",
|
|
51
|
-
options: { baseUrl: "https://api.esa.example.com" },
|
|
52
|
-
} as unknown as Parameters<NonNullable<typeof middleware.onResponse>>[0]);
|
|
53
|
-
|
|
54
|
-
const callOnError = (
|
|
55
|
-
middleware: ReturnType<typeof createAuthMiddleware>,
|
|
56
|
-
error: Error,
|
|
57
|
-
) =>
|
|
58
|
-
middleware.onError?.({
|
|
59
|
-
request: createTestRequest(),
|
|
60
|
-
error,
|
|
61
|
-
schemaPath: "/test",
|
|
62
|
-
params: {},
|
|
63
|
-
id: "test-id",
|
|
64
|
-
options: { baseUrl: "https://api.esa.example.com" },
|
|
65
|
-
} as unknown as Parameters<NonNullable<typeof middleware.onError>>[0]);
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
afterEach(() => {
|
|
72
|
-
consoleErrorSpy.mockRestore();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should add authorization header", async () => {
|
|
76
|
-
const middleware = createAuthMiddleware("test-token");
|
|
77
|
-
const request = createTestRequest();
|
|
78
|
-
|
|
79
|
-
expect(middleware.onRequest).toBeDefined();
|
|
80
|
-
expect(request.headers.get("Authorization")).toBeNull();
|
|
81
|
-
|
|
82
|
-
await callOnRequest(middleware, request);
|
|
83
|
-
|
|
84
|
-
expect(request.headers.get("Authorization")).toBe("Bearer test-token");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("should log rate limit info when present", async () => {
|
|
88
|
-
const middleware = createAuthMiddleware("test-token");
|
|
89
|
-
const response = createTestResponse({ limit: "75", remaining: "50" });
|
|
90
|
-
|
|
91
|
-
await callOnResponse(middleware, response);
|
|
92
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith("Rate limit: 50/75");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should handle response without rate limit headers", async () => {
|
|
96
|
-
const middleware = createAuthMiddleware("test-token");
|
|
97
|
-
const response = createTestResponse();
|
|
98
|
-
|
|
99
|
-
await callOnResponse(middleware, response);
|
|
100
|
-
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("should log network errors", async () => {
|
|
104
|
-
const middleware = createAuthMiddleware("test-token");
|
|
105
|
-
const error = new Error("Network failed");
|
|
106
|
-
|
|
107
|
-
await callOnError(middleware, error);
|
|
108
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith("Network Error:", error);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should return the modified request", async () => {
|
|
112
|
-
const middleware = createAuthMiddleware("test-token");
|
|
113
|
-
const request = createTestRequest();
|
|
114
|
-
|
|
115
|
-
const result = await callOnRequest(middleware, request);
|
|
116
|
-
|
|
117
|
-
expect(result).toBe(request);
|
|
118
|
-
expect(request.headers.get("Authorization")).toBe("Bearer test-token");
|
|
119
|
-
});
|
|
120
|
-
});
|