@better-i18n/mcp 0.15.6 → 0.17.0

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.
Files changed (90) hide show
  1. package/dist/tools/createKeys.d.ts.map +1 -1
  2. package/dist/tools/createKeys.js +14 -0
  3. package/dist/tools/createKeys.js.map +1 -1
  4. package/dist/tools/proposeLanguageEdits.d.ts.map +1 -1
  5. package/dist/tools/proposeLanguageEdits.js +10 -0
  6. package/dist/tools/proposeLanguageEdits.js.map +1 -1
  7. package/dist/tools/proposeLanguages.d.ts.map +1 -1
  8. package/dist/tools/proposeLanguages.js +4 -0
  9. package/dist/tools/proposeLanguages.js.map +1 -1
  10. package/package.json +3 -2
  11. package/dist/__tests__/base-tool.test.d.ts +0 -2
  12. package/dist/__tests__/base-tool.test.d.ts.map +0 -1
  13. package/dist/__tests__/base-tool.test.js +0 -193
  14. package/dist/__tests__/base-tool.test.js.map +0 -1
  15. package/dist/__tests__/contract.test.d.ts +0 -24
  16. package/dist/__tests__/contract.test.d.ts.map +0 -1
  17. package/dist/__tests__/contract.test.js +0 -919
  18. package/dist/__tests__/contract.test.js.map +0 -1
  19. package/dist/__tests__/fixtures/mock-client.d.ts +0 -11
  20. package/dist/__tests__/fixtures/mock-client.d.ts.map +0 -1
  21. package/dist/__tests__/fixtures/mock-client.js +0 -33
  22. package/dist/__tests__/fixtures/mock-client.js.map +0 -1
  23. package/dist/__tests__/helpers.d.ts +0 -10
  24. package/dist/__tests__/helpers.d.ts.map +0 -1
  25. package/dist/__tests__/helpers.js +0 -27
  26. package/dist/__tests__/helpers.js.map +0 -1
  27. package/dist/__tests__/schema-alignment.test.d.ts +0 -15
  28. package/dist/__tests__/schema-alignment.test.d.ts.map +0 -1
  29. package/dist/__tests__/schema-alignment.test.js +0 -1011
  30. package/dist/__tests__/schema-alignment.test.js.map +0 -1
  31. package/dist/__tests__/server-dispatch.test.d.ts +0 -10
  32. package/dist/__tests__/server-dispatch.test.d.ts.map +0 -1
  33. package/dist/__tests__/server-dispatch.test.js +0 -202
  34. package/dist/__tests__/server-dispatch.test.js.map +0 -1
  35. package/dist/__tests__/version-check.test.d.ts +0 -2
  36. package/dist/__tests__/version-check.test.d.ts.map +0 -1
  37. package/dist/__tests__/version-check.test.js +0 -89
  38. package/dist/__tests__/version-check.test.js.map +0 -1
  39. package/dist/tools/__tests__/createKeys.test.d.ts +0 -2
  40. package/dist/tools/__tests__/createKeys.test.d.ts.map +0 -1
  41. package/dist/tools/__tests__/createKeys.test.js +0 -139
  42. package/dist/tools/__tests__/createKeys.test.js.map +0 -1
  43. package/dist/tools/__tests__/deleteKeys.test.d.ts +0 -2
  44. package/dist/tools/__tests__/deleteKeys.test.d.ts.map +0 -1
  45. package/dist/tools/__tests__/deleteKeys.test.js +0 -91
  46. package/dist/tools/__tests__/deleteKeys.test.js.map +0 -1
  47. package/dist/tools/__tests__/getPendingChanges.test.d.ts +0 -2
  48. package/dist/tools/__tests__/getPendingChanges.test.d.ts.map +0 -1
  49. package/dist/tools/__tests__/getPendingChanges.test.js +0 -50
  50. package/dist/tools/__tests__/getPendingChanges.test.js.map +0 -1
  51. package/dist/tools/__tests__/getProject.test.d.ts +0 -2
  52. package/dist/tools/__tests__/getProject.test.d.ts.map +0 -1
  53. package/dist/tools/__tests__/getProject.test.js +0 -55
  54. package/dist/tools/__tests__/getProject.test.js.map +0 -1
  55. package/dist/tools/__tests__/getSync.test.d.ts +0 -2
  56. package/dist/tools/__tests__/getSync.test.d.ts.map +0 -1
  57. package/dist/tools/__tests__/getSync.test.js +0 -42
  58. package/dist/tools/__tests__/getSync.test.js.map +0 -1
  59. package/dist/tools/__tests__/getSyncs.test.d.ts +0 -2
  60. package/dist/tools/__tests__/getSyncs.test.d.ts.map +0 -1
  61. package/dist/tools/__tests__/getSyncs.test.js +0 -66
  62. package/dist/tools/__tests__/getSyncs.test.js.map +0 -1
  63. package/dist/tools/__tests__/getTranslations.test.d.ts +0 -2
  64. package/dist/tools/__tests__/getTranslations.test.d.ts.map +0 -1
  65. package/dist/tools/__tests__/getTranslations.test.js +0 -114
  66. package/dist/tools/__tests__/getTranslations.test.js.map +0 -1
  67. package/dist/tools/__tests__/listKeys.test.d.ts +0 -2
  68. package/dist/tools/__tests__/listKeys.test.d.ts.map +0 -1
  69. package/dist/tools/__tests__/listKeys.test.js +0 -98
  70. package/dist/tools/__tests__/listKeys.test.js.map +0 -1
  71. package/dist/tools/__tests__/listProjects.test.d.ts +0 -2
  72. package/dist/tools/__tests__/listProjects.test.d.ts.map +0 -1
  73. package/dist/tools/__tests__/listProjects.test.js +0 -45
  74. package/dist/tools/__tests__/listProjects.test.js.map +0 -1
  75. package/dist/tools/__tests__/proposeLanguageEdits.test.d.ts +0 -2
  76. package/dist/tools/__tests__/proposeLanguageEdits.test.d.ts.map +0 -1
  77. package/dist/tools/__tests__/proposeLanguageEdits.test.js +0 -87
  78. package/dist/tools/__tests__/proposeLanguageEdits.test.js.map +0 -1
  79. package/dist/tools/__tests__/proposeLanguages.test.d.ts +0 -2
  80. package/dist/tools/__tests__/proposeLanguages.test.d.ts.map +0 -1
  81. package/dist/tools/__tests__/proposeLanguages.test.js +0 -109
  82. package/dist/tools/__tests__/proposeLanguages.test.js.map +0 -1
  83. package/dist/tools/__tests__/publishTranslations.test.d.ts +0 -2
  84. package/dist/tools/__tests__/publishTranslations.test.d.ts.map +0 -1
  85. package/dist/tools/__tests__/publishTranslations.test.js +0 -127
  86. package/dist/tools/__tests__/publishTranslations.test.js.map +0 -1
  87. package/dist/tools/__tests__/updateKeys.test.d.ts +0 -2
  88. package/dist/tools/__tests__/updateKeys.test.d.ts.map +0 -1
  89. package/dist/tools/__tests__/updateKeys.test.js +0 -122
  90. package/dist/tools/__tests__/updateKeys.test.js.map +0 -1
@@ -1,919 +0,0 @@
1
- /**
2
- * contract.test.ts
3
- *
4
- * End-to-end contract tests that bridge the gap between tool logic and the real API.
5
- *
6
- * THREE test sections:
7
- *
8
- * 1. FORWARD CONTRACT — tool output → API schema validation
9
- * Execute each tool with a mock client, capture what it ACTUALLY sends to the
10
- * API (orgSlug, projectSlug, payload), and validate those args against the
11
- * mcp-types Zod schemas (the real API contract). If the API schema rejects it,
12
- * the tool has a normalization or mapping bug.
13
- *
14
- * 2. REVERSE CONTRACT — LLM-plausible args → tool acceptance
15
- * Simulate inputs an LLM would plausibly generate from reading inputSchema,
16
- * and assert the tool accepts them without isError. Catches regressions where
17
- * a schema tightening breaks something an LLM was expected to send.
18
- *
19
- * 3. NEGATIVE CONTRACT — invalid args MUST be rejected
20
- * Guard rails: assert that clearly invalid inputs are rejected (isError=true)
21
- * so we never silently swallow bad data from an LLM.
22
- */
23
- import { describe, it, expect, vi } from "vitest";
24
- import { createMockClient } from "./fixtures/mock-client.js";
25
- // ── Tool imports ──────────────────────────────────────────────────────────────
26
- import { createKeys } from "../tools/createKeys.js";
27
- import { updateKeys } from "../tools/updateKeys.js";
28
- import { deleteKeys } from "../tools/deleteKeys.js";
29
- import { publishTranslations } from "../tools/publishTranslations.js";
30
- import { listKeys } from "../tools/listKeys.js";
31
- import { getTranslations } from "../tools/getTranslations.js";
32
- import { listProjects } from "../tools/listProjects.js";
33
- import { getProject } from "../tools/getProject.js";
34
- import { getPendingChanges } from "../tools/getPendingChanges.js";
35
- import { proposeLanguages } from "../tools/proposeLanguages.js";
36
- import { proposeLanguageEdits } from "../tools/proposeLanguageEdits.js";
37
- import { getSyncs } from "../tools/getSyncs.js";
38
- import { getSync } from "../tools/getSync.js";
39
- // ── mcp-types API schemas (the real API contract) ────────────────────────────
40
- import { createKeysInput, updateKeysInput, deleteKeysInput, listKeysInput, getTranslationsInput, addLanguagesInput, updateLanguagesInput, getSyncsInput, getSyncInput, publishInput, getProjectInput, getPendingChangesInput, } from "@better-i18n/mcp-types/schemas";
41
- // ── Stub responses (minimal valid shapes, tools don't care about their content) ──
42
- const STUBS = {
43
- createKeys: { ok: true, cnt: 1, new: 1, ren: 0, dup: 0, k: [] },
44
- updateKeys: { ok: true, cnt: 1, upd: [] },
45
- deleteKeys: { ok: true, cnt: 1, mk: [] },
46
- publishTranslations: { success: true },
47
- listKeys: { tot: 0, ret: 0, pg: 1, lim: 20, has_more: false, nss: [], k: [] },
48
- getAllTranslations: { prj: "org/proj", sl: "en", ret: 0, tot: 0, has_more: false, keys: [] },
49
- getProject: { prj: "org/proj", sl: "en", nss: [], lng: [], tk: 0, cov: {} },
50
- getPendingChanges: { prj: "org/proj", has_chg: false, sum: { tr: 0, del_k: 0, lng_chg: 0, tot: 0 }, by_lng: {}, del_k: [], pub_dst: "cdn" },
51
- addLanguages: { success: true, added: 1, skipped: 0, results: [] },
52
- updateLanguages: { success: true, results: [], notFound: [] },
53
- getSyncs: { prj: "org/proj", tot: 0, sy: [] },
54
- getSync: { id: "sync-1", tp: "source_sync", st: "completed", st_at: "2024-01-01", log: [], aff_k: [] },
55
- listProjects: [],
56
- };
57
- // ─────────────────────────────────────────────────────────────────────────────
58
- // 1. FORWARD CONTRACT: Tool output → API schema validation
59
- // ─────────────────────────────────────────────────────────────────────────────
60
- describe("forward contract: tool output → API schema", () => {
61
- // ── createKeys ──────────────────────────────────────────────────────────────
62
- describe("createKeys", () => {
63
- it("API schema accepts what tool sends after normalization", async () => {
64
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
65
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
66
- await createKeys.execute(client, {
67
- project: "my-org/my-proj",
68
- k: [{ n: "greeting", v: "Hello", t: { TR: "Merhaba", DE: "Hallo" } }],
69
- });
70
- const apiArgs = mutateMock.mock.calls[0][0];
71
- const result = createKeysInput.safeParse(apiArgs);
72
- expect(result.success, result.error?.message).toBe(true);
73
- });
74
- it("language codes are lowercased before API call", async () => {
75
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
76
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
77
- await createKeys.execute(client, {
78
- project: "org/proj",
79
- k: [{ n: "test", t: { TR: "val", DE: "val" } }],
80
- });
81
- const apiArgs = mutateMock.mock.calls[0][0];
82
- expect(apiArgs.k[0].t).toEqual({ tr: "val", de: "val" });
83
- const result = createKeysInput.safeParse(apiArgs);
84
- expect(result.success, result.error?.message).toBe(true);
85
- });
86
- it("default namespace is included in API call", async () => {
87
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
88
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
89
- await createKeys.execute(client, {
90
- project: "org/proj",
91
- k: [{ n: "test" }],
92
- });
93
- const apiArgs = mutateMock.mock.calls[0][0];
94
- expect(apiArgs.k[0].ns).toBe("default");
95
- const result = createKeysInput.safeParse(apiArgs);
96
- expect(result.success, result.error?.message).toBe(true);
97
- });
98
- it("namespace context passes through to API", async () => {
99
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
100
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
101
- await createKeys.execute(client, {
102
- project: "org/proj",
103
- k: [{
104
- n: "test",
105
- nc: {
106
- description: "Auth flow",
107
- team: "auth-team",
108
- domain: "auth",
109
- aiPrompt: "Use formal tone",
110
- tags: ["critical"],
111
- },
112
- }],
113
- });
114
- const apiArgs = mutateMock.mock.calls[0][0];
115
- const result = createKeysInput.safeParse(apiArgs);
116
- expect(result.success, result.error?.message).toBe(true);
117
- });
118
- it("orgSlug and projectSlug are correctly split from project string", async () => {
119
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
120
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
121
- await createKeys.execute(client, {
122
- project: "my-org/my-proj",
123
- k: [{ n: "key" }],
124
- });
125
- const apiArgs = mutateMock.mock.calls[0][0];
126
- expect(apiArgs.orgSlug).toBe("my-org");
127
- expect(apiArgs.projectSlug).toBe("my-proj");
128
- const result = createKeysInput.safeParse(apiArgs);
129
- expect(result.success, result.error?.message).toBe(true);
130
- });
131
- });
132
- // ── updateKeys ──────────────────────────────────────────────────────────────
133
- describe("updateKeys", () => {
134
- it("API schema accepts what tool sends after normalization", async () => {
135
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
136
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
137
- await updateKeys.execute(client, {
138
- project: "org/proj",
139
- t: [{ id: "550e8400-e29b-41d4-a716-446655440000", l: "TR", t: "Merhaba" }],
140
- });
141
- const apiArgs = mutateMock.mock.calls[0][0];
142
- expect(apiArgs.t[0].l).toBe("tr");
143
- const result = updateKeysInput.safeParse(apiArgs);
144
- expect(result.success, result.error?.message).toBe(true);
145
- });
146
- it("source update flag and status pass through correctly", async () => {
147
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
148
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
149
- await updateKeys.execute(client, {
150
- project: "org/proj",
151
- t: [{ id: "550e8400-e29b-41d4-a716-446655440000", l: "en", t: "Updated", s: true, st: "published" }],
152
- });
153
- const apiArgs = mutateMock.mock.calls[0][0];
154
- const result = updateKeysInput.safeParse(apiArgs);
155
- expect(result.success, result.error?.message).toBe(true);
156
- });
157
- it("batch updates accepted by API", async () => {
158
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
159
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
160
- await updateKeys.execute(client, {
161
- project: "org/proj",
162
- t: [
163
- { id: "550e8400-e29b-41d4-a716-446655440000", l: "TR", t: "Merhaba" },
164
- { id: "550e8400-e29b-41d4-a716-446655440001", l: "DE", t: "Hallo" },
165
- ],
166
- });
167
- const apiArgs = mutateMock.mock.calls[0][0];
168
- expect(apiArgs.t[0].l).toBe("tr");
169
- expect(apiArgs.t[1].l).toBe("de");
170
- const result = updateKeysInput.safeParse(apiArgs);
171
- expect(result.success, result.error?.message).toBe(true);
172
- });
173
- });
174
- // ── deleteKeys ──────────────────────────────────────────────────────────────
175
- describe("deleteKeys", () => {
176
- it("API schema accepts what tool sends", async () => {
177
- const mutateMock = vi.fn().mockResolvedValue(STUBS.deleteKeys);
178
- const client = createMockClient({ mcp: { deleteKeys: { mutate: mutateMock } } });
179
- await deleteKeys.execute(client, {
180
- project: "org/proj",
181
- keyIds: ["550e8400-e29b-41d4-a716-446655440000"],
182
- });
183
- const apiArgs = mutateMock.mock.calls[0][0];
184
- const result = deleteKeysInput.safeParse(apiArgs);
185
- expect(result.success, result.error?.message).toBe(true);
186
- });
187
- it("multiple UUIDs accepted by API", async () => {
188
- const mutateMock = vi.fn().mockResolvedValue(STUBS.deleteKeys);
189
- const client = createMockClient({ mcp: { deleteKeys: { mutate: mutateMock } } });
190
- await deleteKeys.execute(client, {
191
- project: "org/proj",
192
- keyIds: [
193
- "550e8400-e29b-41d4-a716-446655440000",
194
- "550e8400-e29b-41d4-a716-446655440001",
195
- "550e8400-e29b-41d4-a716-446655440002",
196
- ],
197
- });
198
- const apiArgs = mutateMock.mock.calls[0][0];
199
- const result = deleteKeysInput.safeParse(apiArgs);
200
- expect(result.success, result.error?.message).toBe(true);
201
- });
202
- });
203
- // ── publishTranslations ─────────────────────────────────────────────────────
204
- describe("publishTranslations", () => {
205
- it("full publish (no translations) accepted by API", async () => {
206
- const mutateMock = vi.fn().mockResolvedValue(STUBS.publishTranslations);
207
- const client = createMockClient({ mcp: { publishTranslations: { mutate: mutateMock } } });
208
- await publishTranslations.execute(client, { project: "org/proj" });
209
- const apiArgs = mutateMock.mock.calls[0][0];
210
- const result = publishInput.safeParse(apiArgs);
211
- expect(result.success, result.error?.message).toBe(true);
212
- });
213
- it("selective publish with normalized language codes accepted by API", async () => {
214
- const mutateMock = vi.fn().mockResolvedValue(STUBS.publishTranslations);
215
- const client = createMockClient({ mcp: { publishTranslations: { mutate: mutateMock } } });
216
- await publishTranslations.execute(client, {
217
- project: "org/proj",
218
- translations: [{ keyId: "550e8400-e29b-41d4-a716-446655440000", languageCode: "TR" }],
219
- });
220
- const apiArgs = mutateMock.mock.calls[0][0];
221
- expect(apiArgs.translations[0].languageCode).toBe("tr");
222
- const result = publishInput.safeParse(apiArgs);
223
- expect(result.success, result.error?.message).toBe(true);
224
- });
225
- it("multiple selective translations accepted by API", async () => {
226
- const mutateMock = vi.fn().mockResolvedValue(STUBS.publishTranslations);
227
- const client = createMockClient({ mcp: { publishTranslations: { mutate: mutateMock } } });
228
- await publishTranslations.execute(client, {
229
- project: "org/proj",
230
- translations: [
231
- { keyId: "550e8400-e29b-41d4-a716-446655440000", languageCode: "tr" },
232
- { keyId: "550e8400-e29b-41d4-a716-446655440001", languageCode: "DE" },
233
- ],
234
- });
235
- const apiArgs = mutateMock.mock.calls[0][0];
236
- expect(apiArgs.translations[1].languageCode).toBe("de");
237
- const result = publishInput.safeParse(apiArgs);
238
- expect(result.success, result.error?.message).toBe(true);
239
- });
240
- });
241
- // ── listKeys ────────────────────────────────────────────────────────────────
242
- describe("listKeys", () => {
243
- it("default pagination accepted by API", async () => {
244
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
245
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
246
- await listKeys.execute(client, { project: "org/proj" });
247
- const apiArgs = queryMock.mock.calls[0][0];
248
- const result = listKeysInput.safeParse(apiArgs);
249
- expect(result.success, result.error?.message).toBe(true);
250
- });
251
- it("all filters accepted by API", async () => {
252
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
253
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
254
- await listKeys.execute(client, {
255
- project: "org/proj",
256
- search: "login",
257
- namespaces: ["auth"],
258
- missingLanguage: "tr",
259
- fields: ["id", "sourceText"],
260
- page: 2,
261
- limit: 50,
262
- });
263
- const apiArgs = queryMock.mock.calls[0][0];
264
- const result = listKeysInput.safeParse(apiArgs);
265
- expect(result.success, result.error?.message).toBe(true);
266
- });
267
- it("array search accepted by API", async () => {
268
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
269
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
270
- await listKeys.execute(client, {
271
- project: "org/proj",
272
- search: ["login", "signup"],
273
- });
274
- const apiArgs = queryMock.mock.calls[0][0];
275
- const result = listKeysInput.safeParse(apiArgs);
276
- expect(result.success, result.error?.message).toBe(true);
277
- });
278
- });
279
- // ── getTranslations ─────────────────────────────────────────────────────────
280
- describe("getTranslations", () => {
281
- it("minimal args (project only) accepted by API", async () => {
282
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
283
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
284
- await getTranslations.execute(client, { project: "org/proj" });
285
- const apiArgs = queryMock.mock.calls[0][0];
286
- const result = getTranslationsInput.safeParse(apiArgs);
287
- expect(result.success, result.error?.message).toBe(true);
288
- });
289
- it("all filters accepted by API", async () => {
290
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
291
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
292
- await getTranslations.execute(client, {
293
- project: "org/proj",
294
- search: "login",
295
- languages: ["tr", "de"],
296
- namespaces: ["auth"],
297
- keys: ["auth.login.title"],
298
- status: "missing",
299
- limit: 50,
300
- });
301
- const apiArgs = queryMock.mock.calls[0][0];
302
- const result = getTranslationsInput.safeParse(apiArgs);
303
- expect(result.success, result.error?.message).toBe(true);
304
- });
305
- });
306
- // ── getProject ──────────────────────────────────────────────────────────────
307
- describe("getProject", () => {
308
- it("API schema accepts what tool sends", async () => {
309
- const queryMock = vi.fn().mockResolvedValue(STUBS.getProject);
310
- const client = createMockClient({ mcp: { getProject: { query: queryMock } } });
311
- await getProject.execute(client, { project: "org/proj" });
312
- const apiArgs = queryMock.mock.calls[0][0];
313
- const result = getProjectInput.safeParse(apiArgs);
314
- expect(result.success, result.error?.message).toBe(true);
315
- });
316
- });
317
- // ── getPendingChanges ────────────────────────────────────────────────────────
318
- describe("getPendingChanges", () => {
319
- it("API schema accepts what tool sends", async () => {
320
- const queryMock = vi.fn().mockResolvedValue(STUBS.getPendingChanges);
321
- const client = createMockClient({ mcp: { getPendingChanges: { query: queryMock } } });
322
- await getPendingChanges.execute(client, { project: "org/proj" });
323
- const apiArgs = queryMock.mock.calls[0][0];
324
- const result = getPendingChangesInput.safeParse(apiArgs);
325
- expect(result.success, result.error?.message).toBe(true);
326
- });
327
- });
328
- // ── proposeLanguages ────────────────────────────────────────────────────────
329
- describe("proposeLanguages", () => {
330
- it("API schema accepts normalized language codes", async () => {
331
- const mutateMock = vi.fn().mockResolvedValue(STUBS.addLanguages);
332
- const client = createMockClient({ mcp: { addLanguages: { mutate: mutateMock } } });
333
- await proposeLanguages.execute(client, {
334
- project: "org/proj",
335
- languages: [{ languageCode: "FR" }, { languageCode: "DE", status: "draft" }],
336
- });
337
- const apiArgs = mutateMock.mock.calls[0][0];
338
- expect(apiArgs.languages[0].languageCode).toBe("fr");
339
- expect(apiArgs.languages[1].languageCode).toBe("de");
340
- const result = addLanguagesInput.safeParse(apiArgs);
341
- expect(result.success, result.error?.message).toBe(true);
342
- });
343
- });
344
- // ── proposeLanguageEdits ────────────────────────────────────────────────────
345
- describe("proposeLanguageEdits", () => {
346
- it("API schema accepts renamed fields (edits→updates, newStatus→status)", async () => {
347
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateLanguages);
348
- const client = createMockClient({ mcp: { updateLanguages: { mutate: mutateMock } } });
349
- await proposeLanguageEdits.execute(client, {
350
- project: "org/proj",
351
- edits: [{ languageCode: "FR", newStatus: "archived" }],
352
- });
353
- const apiArgs = mutateMock.mock.calls[0][0];
354
- // Tool maps edits → updates and newStatus → status
355
- expect(apiArgs.updates[0].status).toBe("archived");
356
- expect(apiArgs.updates[0].languageCode).toBe("fr");
357
- const result = updateLanguagesInput.safeParse(apiArgs);
358
- expect(result.success, result.error?.message).toBe(true);
359
- });
360
- it("multiple language edits accepted by API", async () => {
361
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateLanguages);
362
- const client = createMockClient({ mcp: { updateLanguages: { mutate: mutateMock } } });
363
- await proposeLanguageEdits.execute(client, {
364
- project: "org/proj",
365
- edits: [
366
- { languageCode: "FR", newStatus: "archived" },
367
- { languageCode: "DE", newStatus: "active" },
368
- { languageCode: "JA", newStatus: "draft" },
369
- ],
370
- });
371
- const apiArgs = mutateMock.mock.calls[0][0];
372
- expect(apiArgs.updates).toHaveLength(3);
373
- expect(apiArgs.updates[0].languageCode).toBe("fr");
374
- expect(apiArgs.updates[1].languageCode).toBe("de");
375
- expect(apiArgs.updates[2].languageCode).toBe("ja");
376
- const result = updateLanguagesInput.safeParse(apiArgs);
377
- expect(result.success, result.error?.message).toBe(true);
378
- });
379
- });
380
- // ── getSyncs ────────────────────────────────────────────────────────────────
381
- describe("getSyncs", () => {
382
- it("API schema accepts what tool sends with all filters", async () => {
383
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSyncs);
384
- const client = createMockClient({ mcp: { getSyncs: { query: queryMock } } });
385
- await getSyncs.execute(client, {
386
- project: "org/proj",
387
- limit: 10,
388
- status: "completed",
389
- type: "source_sync",
390
- });
391
- const apiArgs = queryMock.mock.calls[0][0];
392
- const result = getSyncsInput.safeParse(apiArgs);
393
- expect(result.success, result.error?.message).toBe(true);
394
- });
395
- it("minimal args (project only) accepted by API", async () => {
396
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSyncs);
397
- const client = createMockClient({ mcp: { getSyncs: { query: queryMock } } });
398
- await getSyncs.execute(client, { project: "org/proj" });
399
- const apiArgs = queryMock.mock.calls[0][0];
400
- const result = getSyncsInput.safeParse(apiArgs);
401
- expect(result.success, result.error?.message).toBe(true);
402
- });
403
- });
404
- // ── getSync ─────────────────────────────────────────────────────────────────
405
- describe("getSync", () => {
406
- it("API schema accepts what tool sends", async () => {
407
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSync);
408
- const client = createMockClient({ mcp: { getSync: { query: queryMock } } });
409
- await getSync.execute(client, { syncId: "sync-123" });
410
- const apiArgs = queryMock.mock.calls[0][0];
411
- const result = getSyncInput.safeParse(apiArgs);
412
- expect(result.success, result.error?.message).toBe(true);
413
- });
414
- });
415
- });
416
- // ─────────────────────────────────────────────────────────────────────────────
417
- // 2. REVERSE CONTRACT: LLM-plausible args → tool acceptance
418
- // ─────────────────────────────────────────────────────────────────────────────
419
- describe("reverse contract: LLM-plausible args → tool acceptance", () => {
420
- // ── createKeys ──────────────────────────────────────────────────────────────
421
- describe("createKeys — LLM perspective", () => {
422
- it("accepts minimal args (only required fields from inputSchema)", async () => {
423
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
424
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
425
- const result = await createKeys.execute(client, {
426
- project: "org/proj",
427
- k: [{ n: "hello" }],
428
- });
429
- expect(result.isError).toBeUndefined();
430
- });
431
- it("accepts full args with all optional fields", async () => {
432
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
433
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
434
- const result = await createKeys.execute(client, {
435
- project: "org/proj",
436
- k: [{
437
- n: "auth.login.title",
438
- ns: "auth",
439
- v: "Log In",
440
- t: { tr: "Giriş Yap", de: "Anmelden" },
441
- nc: {
442
- description: "Auth namespace",
443
- team: "auth",
444
- domain: "auth",
445
- aiPrompt: "Formal tone",
446
- tags: ["user-facing"],
447
- },
448
- }],
449
- });
450
- expect(result.isError).toBeUndefined();
451
- });
452
- it("accepts multiple keys in single call", async () => {
453
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
454
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
455
- const result = await createKeys.execute(client, {
456
- project: "org/proj",
457
- k: [
458
- { n: "btn.submit", v: "Submit" },
459
- { n: "btn.cancel", v: "Cancel" },
460
- { n: "btn.save", ns: "common", v: "Save" },
461
- ],
462
- });
463
- expect(result.isError).toBeUndefined();
464
- });
465
- it("LLM might send uppercase language codes (common mistake) — normalized correctly", async () => {
466
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
467
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
468
- const result = await createKeys.execute(client, {
469
- project: "org/proj",
470
- k: [{ n: "test", t: { TR: "Merhaba", DE: "Hallo", FR: "Bonjour" } }],
471
- });
472
- expect(result.isError).toBeUndefined();
473
- // AND verify the API would accept the normalized version
474
- const apiArgs = mutateMock.mock.calls[0][0];
475
- expect(Object.keys(apiArgs.k[0].t)).toEqual(["tr", "de", "fr"]);
476
- });
477
- it("accepts BCP 47 locale codes in translations", async () => {
478
- const mutateMock = vi.fn().mockResolvedValue(STUBS.createKeys);
479
- const client = createMockClient({ mcp: { createKeys: { mutate: mutateMock } } });
480
- const result = await createKeys.execute(client, {
481
- project: "org/proj",
482
- k: [{ n: "greeting", t: { "zh-Hans": "你好", "pt-BR": "Olá" } }],
483
- });
484
- expect(result.isError).toBeUndefined();
485
- });
486
- });
487
- // ── updateKeys ──────────────────────────────────────────────────────────────
488
- describe("updateKeys — LLM perspective", () => {
489
- it("accepts minimal single update", async () => {
490
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
491
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
492
- const result = await updateKeys.execute(client, {
493
- project: "org/proj",
494
- t: [{ id: "550e8400-e29b-41d4-a716-446655440000", l: "tr", t: "Merhaba" }],
495
- });
496
- expect(result.isError).toBeUndefined();
497
- });
498
- it("accepts batch update with source flag and status", async () => {
499
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
500
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
501
- const result = await updateKeys.execute(client, {
502
- project: "org/proj",
503
- t: [
504
- { id: "550e8400-e29b-41d4-a716-446655440000", l: "en", t: "Updated", s: true },
505
- { id: "550e8400-e29b-41d4-a716-446655440001", l: "tr", t: "Güncellendi", st: "published" },
506
- ],
507
- });
508
- expect(result.isError).toBeUndefined();
509
- });
510
- it("LLM might send uppercase language (common mistake) — normalized correctly", async () => {
511
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
512
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
513
- const result = await updateKeys.execute(client, {
514
- project: "org/proj",
515
- t: [{ id: "550e8400-e29b-41d4-a716-446655440000", l: "TR", t: "Merhaba" }],
516
- });
517
- expect(result.isError).toBeUndefined();
518
- expect(mutateMock.mock.calls[0][0].t[0].l).toBe("tr");
519
- });
520
- it("accepts non-UUID id (API uses plain string for id)", async () => {
521
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateKeys);
522
- const client = createMockClient({ mcp: { updateKeys: { mutate: mutateMock } } });
523
- // The API schema for updateKeys uses z.string() (not uuid()) for id
524
- const result = await updateKeys.execute(client, {
525
- project: "org/proj",
526
- t: [{ id: "some-plain-string-id", l: "tr", t: "text" }],
527
- });
528
- expect(result.isError).toBeUndefined();
529
- });
530
- });
531
- // ── deleteKeys ──────────────────────────────────────────────────────────────
532
- describe("deleteKeys — LLM perspective", () => {
533
- it("accepts single UUID", async () => {
534
- const mutateMock = vi.fn().mockResolvedValue(STUBS.deleteKeys);
535
- const client = createMockClient({ mcp: { deleteKeys: { mutate: mutateMock } } });
536
- const result = await deleteKeys.execute(client, {
537
- project: "org/proj",
538
- keyIds: ["550e8400-e29b-41d4-a716-446655440000"],
539
- });
540
- expect(result.isError).toBeUndefined();
541
- });
542
- it("accepts batch UUIDs", async () => {
543
- const mutateMock = vi.fn().mockResolvedValue(STUBS.deleteKeys);
544
- const client = createMockClient({ mcp: { deleteKeys: { mutate: mutateMock } } });
545
- const uuids = Array.from({ length: 5 }, (_, i) => `550e8400-e29b-41d4-a716-${String(i).padStart(12, "0")}`);
546
- const result = await deleteKeys.execute(client, {
547
- project: "org/proj",
548
- keyIds: uuids,
549
- });
550
- expect(result.isError).toBeUndefined();
551
- });
552
- });
553
- // ── listKeys ────────────────────────────────────────────────────────────────
554
- describe("listKeys — LLM perspective", () => {
555
- it("accepts project-only (minimal)", async () => {
556
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
557
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
558
- const result = await listKeys.execute(client, { project: "org/proj" });
559
- expect(result.isError).toBeUndefined();
560
- });
561
- it("accepts all filter combinations", async () => {
562
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
563
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
564
- const result = await listKeys.execute(client, {
565
- project: "org/proj",
566
- search: ["login", "signup"],
567
- namespaces: ["auth", "common"],
568
- missingLanguage: "tr",
569
- fields: ["id", "sourceText", "translatedLanguages"],
570
- page: 2,
571
- limit: 50,
572
- });
573
- expect(result.isError).toBeUndefined();
574
- });
575
- it("accepts translatedLanguageCount field", async () => {
576
- const queryMock = vi.fn().mockResolvedValue(STUBS.listKeys);
577
- const client = createMockClient({ mcp: { listKeys: { query: queryMock } } });
578
- const result = await listKeys.execute(client, {
579
- project: "org/proj",
580
- fields: ["id", "translatedLanguageCount"],
581
- });
582
- expect(result.isError).toBeUndefined();
583
- });
584
- });
585
- // ── getTranslations ─────────────────────────────────────────────────────────
586
- describe("getTranslations — LLM perspective", () => {
587
- it("accepts status filter with languages (correct usage)", async () => {
588
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
589
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
590
- const result = await getTranslations.execute(client, {
591
- project: "org/proj",
592
- languages: ["tr"],
593
- status: "missing",
594
- limit: 50,
595
- });
596
- expect(result.isError).toBeUndefined();
597
- });
598
- it("accepts multi-term search array", async () => {
599
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
600
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
601
- const result = await getTranslations.execute(client, {
602
- project: "org/proj",
603
- search: ["login", "signup", "forgot_password"],
604
- });
605
- expect(result.isError).toBeUndefined();
606
- });
607
- it("accepts specific keys lookup", async () => {
608
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
609
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
610
- const result = await getTranslations.execute(client, {
611
- project: "org/proj",
612
- keys: ["auth.login.title", "auth.login.button"],
613
- });
614
- expect(result.isError).toBeUndefined();
615
- });
616
- it("accepts all status values", async () => {
617
- const queryMock = vi.fn().mockResolvedValue(STUBS.getAllTranslations);
618
- const client = createMockClient({ mcp: { getAllTranslations: { query: queryMock } } });
619
- for (const status of ["missing", "draft", "published", "all"]) {
620
- queryMock.mockClear();
621
- const result = await getTranslations.execute(client, {
622
- project: "org/proj",
623
- languages: ["tr"],
624
- status,
625
- });
626
- expect(result.isError, `status="${status}" should be accepted`).toBeUndefined();
627
- }
628
- });
629
- });
630
- // ── publishTranslations ─────────────────────────────────────────────────────
631
- describe("publishTranslations — LLM perspective", () => {
632
- it("accepts project-only for full publish", async () => {
633
- const mutateMock = vi.fn().mockResolvedValue(STUBS.publishTranslations);
634
- const client = createMockClient({ mcp: { publishTranslations: { mutate: mutateMock } } });
635
- const result = await publishTranslations.execute(client, { project: "org/proj" });
636
- expect(result.isError).toBeUndefined();
637
- });
638
- it("accepts selective publish", async () => {
639
- const mutateMock = vi.fn().mockResolvedValue(STUBS.publishTranslations);
640
- const client = createMockClient({ mcp: { publishTranslations: { mutate: mutateMock } } });
641
- const result = await publishTranslations.execute(client, {
642
- project: "org/proj",
643
- translations: [
644
- { keyId: "550e8400-e29b-41d4-a716-446655440000", languageCode: "tr" },
645
- { keyId: "550e8400-e29b-41d4-a716-446655440001", languageCode: "de" },
646
- ],
647
- });
648
- expect(result.isError).toBeUndefined();
649
- });
650
- });
651
- // ── proposeLanguages ────────────────────────────────────────────────────────
652
- describe("proposeLanguages — LLM perspective", () => {
653
- it("accepts multiple languages", async () => {
654
- const mutateMock = vi.fn().mockResolvedValue(STUBS.addLanguages);
655
- const client = createMockClient({ mcp: { addLanguages: { mutate: mutateMock } } });
656
- const result = await proposeLanguages.execute(client, {
657
- project: "org/proj",
658
- languages: [
659
- { languageCode: "fr" },
660
- { languageCode: "de", status: "draft" },
661
- { languageCode: "ja" },
662
- ],
663
- });
664
- expect(result.isError).toBeUndefined();
665
- });
666
- it("accepts BCP 47 locale codes", async () => {
667
- const mutateMock = vi.fn().mockResolvedValue(STUBS.addLanguages);
668
- const client = createMockClient({ mcp: { addLanguages: { mutate: mutateMock } } });
669
- const result = await proposeLanguages.execute(client, {
670
- project: "org/proj",
671
- languages: [
672
- { languageCode: "zh-Hans" },
673
- { languageCode: "pt-BR" },
674
- ],
675
- });
676
- expect(result.isError).toBeUndefined();
677
- });
678
- });
679
- // ── proposeLanguageEdits ────────────────────────────────────────────────────
680
- describe("proposeLanguageEdits — LLM perspective", () => {
681
- it("accepts status changes", async () => {
682
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateLanguages);
683
- const client = createMockClient({ mcp: { updateLanguages: { mutate: mutateMock } } });
684
- const result = await proposeLanguageEdits.execute(client, {
685
- project: "org/proj",
686
- edits: [
687
- { languageCode: "fr", newStatus: "archived" },
688
- { languageCode: "de", newStatus: "active" },
689
- ],
690
- });
691
- expect(result.isError).toBeUndefined();
692
- });
693
- it("accepts all valid status values", async () => {
694
- const mutateMock = vi.fn().mockResolvedValue(STUBS.updateLanguages);
695
- const client = createMockClient({ mcp: { updateLanguages: { mutate: mutateMock } } });
696
- for (const newStatus of ["active", "draft", "archived"]) {
697
- mutateMock.mockClear();
698
- const result = await proposeLanguageEdits.execute(client, {
699
- project: "org/proj",
700
- edits: [{ languageCode: "fr", newStatus }],
701
- });
702
- expect(result.isError, `newStatus="${newStatus}" should be accepted`).toBeUndefined();
703
- }
704
- });
705
- });
706
- // ── getSyncs ────────────────────────────────────────────────────────────────
707
- describe("getSyncs — LLM perspective", () => {
708
- it("accepts all filter combinations", async () => {
709
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSyncs);
710
- const client = createMockClient({ mcp: { getSyncs: { query: queryMock } } });
711
- const result = await getSyncs.execute(client, {
712
- project: "org/proj",
713
- limit: 10,
714
- status: "failed",
715
- type: "cdn_upload",
716
- });
717
- expect(result.isError).toBeUndefined();
718
- });
719
- it("accepts project-only (minimal)", async () => {
720
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSyncs);
721
- const client = createMockClient({ mcp: { getSyncs: { query: queryMock } } });
722
- const result = await getSyncs.execute(client, { project: "org/proj" });
723
- expect(result.isError).toBeUndefined();
724
- });
725
- });
726
- // ── getSync ─────────────────────────────────────────────────────────────────
727
- describe("getSync — LLM perspective", () => {
728
- it("accepts syncId", async () => {
729
- const queryMock = vi.fn().mockResolvedValue(STUBS.getSync);
730
- const client = createMockClient({ mcp: { getSync: { query: queryMock } } });
731
- const result = await getSync.execute(client, { syncId: "abc-123" });
732
- expect(result.isError).toBeUndefined();
733
- });
734
- });
735
- // ── listProjects ────────────────────────────────────────────────────────────
736
- describe("listProjects — LLM perspective", () => {
737
- it("accepts empty args", async () => {
738
- const queryMock = vi.fn().mockResolvedValue(STUBS.listProjects);
739
- const client = createMockClient({ mcp: { listProjects: { query: queryMock } } });
740
- const result = await listProjects.execute(client, {});
741
- expect(result.isError).toBeUndefined();
742
- });
743
- it("accepts args with no recognized properties (extra fields ignored)", async () => {
744
- const queryMock = vi.fn().mockResolvedValue(STUBS.listProjects);
745
- const client = createMockClient({ mcp: { listProjects: { query: queryMock } } });
746
- // LLMs sometimes add extra fields — should be harmless (stripped by Zod)
747
- const result = await listProjects.execute(client, {});
748
- expect(result.isError).toBeUndefined();
749
- });
750
- });
751
- });
752
- // ─────────────────────────────────────────────────────────────────────────────
753
- // 3. NEGATIVE CONTRACT: invalid args MUST be rejected
754
- // ─────────────────────────────────────────────────────────────────────────────
755
- describe("negative contract: invalid args rejected", () => {
756
- it("rejects project without slash", async () => {
757
- const client = createMockClient();
758
- const result = await createKeys.execute(client, {
759
- project: "no-slash",
760
- k: [{ n: "test" }],
761
- });
762
- expect(result.isError).toBe(true);
763
- });
764
- it("rejects project as empty string", async () => {
765
- const client = createMockClient();
766
- const result = await createKeys.execute(client, {
767
- project: "",
768
- k: [{ n: "test" }],
769
- });
770
- expect(result.isError).toBe(true);
771
- });
772
- it("rejects empty key name in createKeys", async () => {
773
- const client = createMockClient();
774
- const result = await createKeys.execute(client, {
775
- project: "org/proj",
776
- k: [{ n: "" }],
777
- });
778
- expect(result.isError).toBe(true);
779
- });
780
- it("rejects missing k array in createKeys", async () => {
781
- const client = createMockClient();
782
- const result = await createKeys.execute(client, { project: "org/proj" });
783
- expect(result.isError).toBe(true);
784
- });
785
- it("rejects empty k array in createKeys", async () => {
786
- const client = createMockClient();
787
- const result = await createKeys.execute(client, {
788
- project: "org/proj",
789
- k: [],
790
- });
791
- expect(result.isError).toBe(true);
792
- });
793
- it("rejects non-UUID keyIds in deleteKeys", async () => {
794
- const client = createMockClient();
795
- const result = await deleteKeys.execute(client, {
796
- project: "org/proj",
797
- keyIds: ["not-a-uuid"],
798
- });
799
- expect(result.isError).toBe(true);
800
- });
801
- it("rejects empty keyIds array in deleteKeys", async () => {
802
- const client = createMockClient();
803
- const result = await deleteKeys.execute(client, {
804
- project: "org/proj",
805
- keyIds: [],
806
- });
807
- expect(result.isError).toBe(true);
808
- });
809
- it("rejects > 100 keyIds in deleteKeys", async () => {
810
- const client = createMockClient();
811
- const uuids = Array.from({ length: 101 }, (_, i) => `550e8400-e29b-41d4-a716-${String(i).padStart(12, "0")}`);
812
- const result = await deleteKeys.execute(client, {
813
- project: "org/proj",
814
- keyIds: uuids,
815
- });
816
- expect(result.isError).toBe(true);
817
- });
818
- it("rejects limit > 100 in listKeys", async () => {
819
- const client = createMockClient();
820
- const result = await listKeys.execute(client, {
821
- project: "org/proj",
822
- limit: 101,
823
- });
824
- expect(result.isError).toBe(true);
825
- });
826
- it("rejects limit > 200 in getTranslations", async () => {
827
- const client = createMockClient();
828
- const result = await getTranslations.execute(client, {
829
- project: "org/proj",
830
- limit: 201,
831
- });
832
- expect(result.isError).toBe(true);
833
- });
834
- it("rejects invalid status in getTranslations", async () => {
835
- const client = createMockClient();
836
- const result = await getTranslations.execute(client, {
837
- project: "org/proj",
838
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
839
- status: "invalid",
840
- });
841
- expect(result.isError).toBe(true);
842
- });
843
- it("rejects empty t array in updateKeys", async () => {
844
- const client = createMockClient();
845
- const result = await updateKeys.execute(client, {
846
- project: "org/proj",
847
- t: [],
848
- });
849
- expect(result.isError).toBe(true);
850
- });
851
- it("rejects missing t field in updateKeys item", async () => {
852
- const client = createMockClient();
853
- const result = await updateKeys.execute(client, {
854
- project: "org/proj",
855
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
856
- t: [{ id: "550e8400-e29b-41d4-a716-446655440000", l: "tr" }],
857
- });
858
- expect(result.isError).toBe(true);
859
- });
860
- it("rejects empty languages array in proposeLanguages", async () => {
861
- const client = createMockClient();
862
- const result = await proposeLanguages.execute(client, {
863
- project: "org/proj",
864
- languages: [],
865
- });
866
- expect(result.isError).toBe(true);
867
- });
868
- it("rejects invalid status in proposeLanguageEdits", async () => {
869
- const client = createMockClient();
870
- const result = await proposeLanguageEdits.execute(client, {
871
- project: "org/proj",
872
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
873
- edits: [{ languageCode: "fr", newStatus: "unknown-status" }],
874
- });
875
- expect(result.isError).toBe(true);
876
- });
877
- it("rejects empty edits array in proposeLanguageEdits", async () => {
878
- const client = createMockClient();
879
- const result = await proposeLanguageEdits.execute(client, {
880
- project: "org/proj",
881
- edits: [],
882
- });
883
- expect(result.isError).toBe(true);
884
- });
885
- it("rejects limit > 50 in getSyncs", async () => {
886
- const client = createMockClient();
887
- const result = await getSyncs.execute(client, {
888
- project: "org/proj",
889
- limit: 51,
890
- });
891
- expect(result.isError).toBe(true);
892
- });
893
- it("rejects invalid type in getSyncs", async () => {
894
- const client = createMockClient();
895
- const result = await getSyncs.execute(client, {
896
- project: "org/proj",
897
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
898
- type: "not_a_valid_type",
899
- });
900
- expect(result.isError).toBe(true);
901
- });
902
- it("rejects page < 1 in listKeys", async () => {
903
- const client = createMockClient();
904
- const result = await listKeys.execute(client, {
905
- project: "org/proj",
906
- page: 0,
907
- });
908
- expect(result.isError).toBe(true);
909
- });
910
- it("rejects limit < 1 in getTranslations", async () => {
911
- const client = createMockClient();
912
- const result = await getTranslations.execute(client, {
913
- project: "org/proj",
914
- limit: 0,
915
- });
916
- expect(result.isError).toBe(true);
917
- });
918
- });
919
- //# sourceMappingURL=contract.test.js.map