@aigne/doc-smith 0.8.5 → 0.8.7
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/.aigne/doc-smith/output/structure-plan.json +1 -5
- package/CHANGELOG.md +25 -0
- package/README.md +3 -3
- package/agents/{chat.yaml → chat/index.yaml} +7 -7
- package/agents/generate/check-document-structure.yaml +30 -0
- package/agents/{check-structure-plan.mjs → generate/check-need-generate-structure.mjs} +21 -21
- package/agents/generate/generate-structure.yaml +58 -0
- package/agents/{docs-generator.yaml → generate/index.yaml} +15 -16
- package/agents/generate/refine-document-structure.yaml +12 -0
- package/agents/{input-generator.mjs → init/index.mjs} +34 -27
- package/agents/{manage-prefs.mjs → prefs/index.mjs} +16 -16
- package/agents/publish/index.yaml +17 -0
- package/agents/{publish-docs.mjs → publish/publish-docs.mjs} +15 -16
- package/agents/schema/{structure-plan-result.yaml → document-execution-structure.yaml} +3 -3
- package/agents/schema/document-structure.yaml +26 -0
- package/agents/{language-selector.mjs → translate/choose-language.mjs} +5 -5
- package/agents/{retranslate.yaml → translate/index.yaml} +17 -18
- package/agents/translate/translate-document.yaml +32 -0
- package/agents/{batch-translate.yaml → translate/translate-multilingual.yaml} +5 -5
- package/agents/update/batch-generate-document.yaml +19 -0
- package/agents/{check-detail.mjs → update/check-document.mjs} +16 -16
- package/agents/{detail-generator-and-translate.yaml → update/generate-and-translate-document.yaml} +23 -23
- package/agents/update/generate-document.yaml +50 -0
- package/agents/{detail-regenerator.yaml → update/index.yaml} +16 -17
- package/agents/{action-success.mjs → utils/action-success.mjs} +2 -2
- package/agents/{check-detail-result.mjs → utils/check-detail-result.mjs} +3 -3
- package/agents/{check-feedback-refiner.mjs → utils/check-feedback-refiner.mjs} +6 -6
- package/agents/{find-items-by-paths.mjs → utils/choose-docs.mjs} +25 -10
- package/agents/{docs-fs.yaml → utils/docs-fs-actor.yaml} +3 -1
- package/agents/utils/feedback-refiner.yaml +50 -0
- package/agents/{find-item-by-path.mjs → utils/find-item-by-path.mjs} +17 -7
- package/agents/{find-user-preferences-by-path.mjs → utils/find-user-preferences-by-path.mjs} +1 -1
- package/agents/utils/format-document-structure.mjs +25 -0
- package/agents/{load-sources.mjs → utils/load-sources.mjs} +41 -28
- package/agents/{save-docs.mjs → utils/save-docs.mjs} +16 -16
- package/agents/{save-single-doc.mjs → utils/save-single-doc.mjs} +2 -2
- package/agents/{transform-detail-datasources.mjs → utils/transform-detail-datasources.mjs} +1 -1
- package/aigne.yaml +35 -35
- package/docs/cli-reference.md +1 -1
- package/docs/features-generate-documentation.md +1 -1
- package/docs/features-update-and-refine.md +2 -2
- package/docs-mcp/analyze-docs-relevance.yaml +10 -10
- package/docs-mcp/docs-search.yaml +5 -3
- package/package.json +10 -8
- package/prompts/{document → detail/custom}/custom-code-block.md +6 -6
- package/prompts/detail/custom/custom-components.md +172 -0
- package/prompts/{document → detail}/d2-chart/rules.md +95 -1
- package/prompts/{document → detail}/detail-example.md +80 -61
- package/prompts/{document/detail-generator.md → detail/document-rules.md} +4 -8
- package/prompts/{content-detail-generator.md → detail/generate-document.md} +48 -25
- package/prompts/{check-structure-planning-result.md → structure/check-document-structure.md} +23 -17
- package/prompts/{document/structure-planning.md → structure/document-rules.md} +0 -2
- package/prompts/{structure-planning.md → structure/generate-structure.md} +51 -30
- package/prompts/{document → structure}/structure-example.md +2 -2
- package/prompts/{document → structure}/structure-getting-started.md +2 -2
- package/prompts/translate/glossary.md +6 -0
- package/prompts/{translator.md → translate/translate-document.md} +29 -10
- package/prompts/{feedback-refiner.md → utils/feedback-refiner.md} +8 -8
- package/tests/agents/chat/chat.test.mjs +46 -0
- package/tests/agents/generate/check-document-structure.test.mjs +51 -0
- package/tests/agents/generate/check-need-generate-structure.test.mjs +292 -0
- package/tests/agents/generate/generate-structure.test.mjs +51 -0
- package/tests/{input-generator.test.mjs → agents/init/init.test.mjs} +19 -17
- package/tests/agents/prefs/prefs.test.mjs +431 -0
- package/tests/agents/publish/publish-docs.test.mjs +642 -0
- package/tests/agents/translate/choose-language.test.mjs +311 -0
- package/tests/agents/translate/translate-document.test.mjs +51 -0
- package/tests/agents/update/check-document.test.mjs +523 -0
- package/tests/agents/update/generate-document.test.mjs +51 -0
- package/tests/agents/utils/action-success.test.mjs +54 -0
- package/tests/{check-detail-result.test.mjs → agents/utils/check-detail-result.test.mjs} +98 -98
- package/tests/agents/utils/check-feedback-refiner.test.mjs +478 -0
- package/tests/agents/utils/choose-docs.test.mjs +417 -0
- package/tests/agents/utils/exit.test.mjs +70 -0
- package/tests/agents/utils/feedback-refiner.test.mjs +51 -0
- package/tests/agents/utils/find-item-by-path.test.mjs +526 -0
- package/tests/agents/utils/find-user-preferences-by-path.test.mjs +382 -0
- package/tests/agents/utils/format-document-structure.test.mjs +264 -0
- package/tests/agents/utils/fs.test.mjs +267 -0
- package/tests/{load-sources.test.mjs → agents/utils/load-sources.test.mjs} +153 -25
- package/tests/{save-docs.test.mjs → agents/utils/save-docs.test.mjs} +11 -5
- package/tests/agents/utils/save-output.test.mjs +315 -0
- package/tests/agents/utils/save-single-doc.test.mjs +364 -0
- package/tests/agents/utils/transform-detail-datasources.test.mjs +363 -0
- package/tests/utils/auth-utils.test.mjs +358 -0
- package/tests/utils/blocklet.test.mjs +334 -0
- package/tests/{conflict-resolution.test.mjs → utils/conflict-detector.test.mjs} +3 -3
- package/tests/utils/constants.test.mjs +295 -0
- package/tests/utils/d2-utils.test.mjs +423 -0
- package/tests/utils/deploy.test.mjs +365 -0
- package/tests/utils/docs-finder-utils.test.mjs +625 -0
- package/tests/utils/file-utils.test.mjs +213 -0
- package/tests/{kroki-utils.test.mjs → utils/kroki-utils.test.mjs} +2 -2
- package/tests/utils/load-config.test.mjs +141 -0
- package/tests/{mermaid-validation.test.mjs → utils/mermaid-validator.test.mjs} +2 -2
- package/tests/utils/mock-chat-model.mjs +12 -0
- package/tests/{preferences-utils.test.mjs → utils/preferences-utils.test.mjs} +1 -1
- package/tests/{save-value-to-config.test.mjs → utils/save-value-to-config.test.mjs} +61 -4
- package/tests/utils/utils.test.mjs +939 -0
- package/utils/auth-utils.mjs +1 -1
- package/utils/conflict-detector.mjs +1 -1
- package/utils/constants.mjs +5 -3
- package/utils/d2-utils.mjs +194 -0
- package/utils/deploy.mjs +3 -3
- package/utils/docs-finder-utils.mjs +26 -26
- package/utils/icon-map.mjs +26 -0
- package/{agents → utils}/load-config.mjs +2 -18
- package/utils/markdown-checker.mjs +5 -5
- package/agents/batch-docs-detail-generator.yaml +0 -19
- package/agents/check-structure-planning-result.yaml +0 -30
- package/agents/content-detail-generator.yaml +0 -50
- package/agents/feedback-refiner.yaml +0 -52
- package/agents/format-structure-plan.mjs +0 -25
- package/agents/reflective-structure-planner.yaml +0 -12
- package/agents/schema/structure-plan.yaml +0 -26
- package/agents/structure-planning.yaml +0 -58
- package/agents/team-publish-docs.yaml +0 -18
- package/agents/translate.yaml +0 -31
- package/prompts/document/custom-components.md +0 -104
- package/tests/README.md +0 -93
- package/tests/utils.test.mjs +0 -2067
- /package/agents/{exit.mjs → utils/exit.mjs} +0 -0
- /package/agents/{fs.mjs → utils/fs.mjs} +0 -0
- /package/agents/{save-output.mjs → utils/save-output.mjs} +0 -0
- /package/prompts/{document → detail}/d2-chart/official-examples.md +0 -0
- /package/prompts/{document → detail}/jsx/rules.md +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import findUserPreferencesByPath from "../../../agents/utils/find-user-preferences-by-path.mjs";
|
|
3
|
+
|
|
4
|
+
import * as preferencesUtils from "../../../utils/preferences-utils.mjs";
|
|
5
|
+
|
|
6
|
+
describe("find-user-preferences-by-path", () => {
|
|
7
|
+
// Spies for internal utils
|
|
8
|
+
let getActiveRulesForScopeSpy;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Set up spies for internal utils
|
|
12
|
+
getActiveRulesForScopeSpy = spyOn(preferencesUtils, "getActiveRulesForScope").mockReturnValue(
|
|
13
|
+
[],
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Clear spy call history
|
|
17
|
+
getActiveRulesForScopeSpy.mockClear();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
// Restore all spies
|
|
22
|
+
getActiveRulesForScopeSpy?.mockRestore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// BASIC FUNCTIONALITY TESTS
|
|
26
|
+
test("should return empty preferences when no rules found", async () => {
|
|
27
|
+
const result = await findUserPreferencesByPath({
|
|
28
|
+
path: "/api/users",
|
|
29
|
+
scope: "document",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledTimes(2);
|
|
33
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
34
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("document", ["/api/users"]);
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
userPreferences: "",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should return global rules when only global rules exist", async () => {
|
|
41
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
42
|
+
if (scope === "global") {
|
|
43
|
+
return [
|
|
44
|
+
{ rule: "Always use clear and concise language" },
|
|
45
|
+
{ rule: "Include code examples when relevant" },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await findUserPreferencesByPath({
|
|
52
|
+
path: "/api/users",
|
|
53
|
+
scope: "document",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
userPreferences:
|
|
58
|
+
"Always use clear and concise language\n\nInclude code examples when relevant",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("should return scope rules when only scope rules exist", async () => {
|
|
63
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
64
|
+
if (scope === "document") {
|
|
65
|
+
return [
|
|
66
|
+
{ rule: "Focus on API documentation standards" },
|
|
67
|
+
{ rule: "Include parameter details" },
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
return [];
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const result = await findUserPreferencesByPath({
|
|
74
|
+
path: "/api/users",
|
|
75
|
+
scope: "document",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual({
|
|
79
|
+
userPreferences: "Focus on API documentation standards\n\nInclude parameter details",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("should combine global and scope rules correctly", async () => {
|
|
84
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
85
|
+
if (scope === "global") {
|
|
86
|
+
return [{ rule: "Use consistent terminology" }, { rule: "Maintain professional tone" }];
|
|
87
|
+
}
|
|
88
|
+
if (scope === "document") {
|
|
89
|
+
return [{ rule: "Include version information" }, { rule: "Add deprecation notices" }];
|
|
90
|
+
}
|
|
91
|
+
return [];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = await findUserPreferencesByPath({
|
|
95
|
+
path: "/api/auth",
|
|
96
|
+
scope: "document",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result).toEqual({
|
|
100
|
+
userPreferences:
|
|
101
|
+
"Use consistent terminology\n\nMaintain professional tone\n\nInclude version information\n\nAdd deprecation notices",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// SCOPE-SPECIFIC TESTS
|
|
106
|
+
test("should handle document scope correctly", async () => {
|
|
107
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
108
|
+
if (scope === "global") {
|
|
109
|
+
return [{ rule: "Global rule" }];
|
|
110
|
+
}
|
|
111
|
+
if (scope === "document") {
|
|
112
|
+
return [{ rule: "Document rule" }];
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await findUserPreferencesByPath({
|
|
118
|
+
path: "/docs/getting-started",
|
|
119
|
+
scope: "document",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
123
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("document", ["/docs/getting-started"]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should handle translation scope correctly", async () => {
|
|
127
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
128
|
+
if (scope === "global") {
|
|
129
|
+
return [{ rule: "Global rule" }];
|
|
130
|
+
}
|
|
131
|
+
if (scope === "translation") {
|
|
132
|
+
return [{ rule: "Translation rule" }];
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await findUserPreferencesByPath({
|
|
138
|
+
path: "/docs/api-reference",
|
|
139
|
+
scope: "translation",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
143
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("translation", ["/docs/api-reference"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("should handle missing path parameter", async () => {
|
|
147
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
148
|
+
if (scope === "global") {
|
|
149
|
+
return [{ rule: "Global rule for all" }];
|
|
150
|
+
}
|
|
151
|
+
if (scope === "document") {
|
|
152
|
+
return [{ rule: "Document rule for all" }];
|
|
153
|
+
}
|
|
154
|
+
return [];
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await findUserPreferencesByPath({
|
|
158
|
+
scope: "document",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
162
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("document", []);
|
|
163
|
+
expect(result).toEqual({
|
|
164
|
+
userPreferences: "Global rule for all\n\nDocument rule for all",
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("should handle null path parameter", async () => {
|
|
169
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
170
|
+
if (scope === "global") {
|
|
171
|
+
return [{ rule: "Global rule" }];
|
|
172
|
+
}
|
|
173
|
+
return [];
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = await findUserPreferencesByPath({
|
|
177
|
+
path: null,
|
|
178
|
+
scope: "translation",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
182
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("translation", []);
|
|
183
|
+
expect(result).toEqual({
|
|
184
|
+
userPreferences: "Global rule",
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("should handle empty path parameter", async () => {
|
|
189
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
190
|
+
if (scope === "global") {
|
|
191
|
+
return [{ rule: "Global rule" }];
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const result = await findUserPreferencesByPath({
|
|
197
|
+
path: "",
|
|
198
|
+
scope: "document",
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
202
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("document", []);
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
userPreferences: "Global rule",
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// RULE COMBINATION TESTS
|
|
209
|
+
test("should handle single rule correctly", async () => {
|
|
210
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
211
|
+
if (scope === "global") {
|
|
212
|
+
return [{ rule: "Single global rule" }];
|
|
213
|
+
}
|
|
214
|
+
return [];
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await findUserPreferencesByPath({
|
|
218
|
+
path: "/test",
|
|
219
|
+
scope: "document",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual({
|
|
223
|
+
userPreferences: "Single global rule",
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("should handle multiple rules with proper formatting", async () => {
|
|
228
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
229
|
+
if (scope === "global") {
|
|
230
|
+
return [{ rule: "First rule" }, { rule: "Second rule" }, { rule: "Third rule" }];
|
|
231
|
+
}
|
|
232
|
+
return [];
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const result = await findUserPreferencesByPath({
|
|
236
|
+
path: "/test",
|
|
237
|
+
scope: "document",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(result).toEqual({
|
|
241
|
+
userPreferences: "First rule\n\nSecond rule\n\nThird rule",
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("should handle rules with complex text", async () => {
|
|
246
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
247
|
+
if (scope === "global") {
|
|
248
|
+
return [
|
|
249
|
+
{
|
|
250
|
+
rule: "When writing API documentation, always include:\n- Request parameters\n- Response examples\n- Error codes",
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
if (scope === "document") {
|
|
255
|
+
return [
|
|
256
|
+
{
|
|
257
|
+
rule: "For authentication endpoints:\n1. Mention security considerations\n2. Include rate limiting info",
|
|
258
|
+
},
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
return [];
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const result = await findUserPreferencesByPath({
|
|
265
|
+
path: "/api/auth/login",
|
|
266
|
+
scope: "document",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(result.userPreferences).toContain("When writing API documentation");
|
|
270
|
+
expect(result.userPreferences).toContain("For authentication endpoints");
|
|
271
|
+
expect(result.userPreferences).toContain("\n\n");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// EDGE CASES
|
|
275
|
+
test("should handle undefined scope parameter gracefully", async () => {
|
|
276
|
+
const result = await findUserPreferencesByPath({
|
|
277
|
+
path: "/test",
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("global", []);
|
|
281
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith(undefined, ["/test"]);
|
|
282
|
+
expect(result).toEqual({
|
|
283
|
+
userPreferences: "",
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("should handle rules with empty rule text", async () => {
|
|
288
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
289
|
+
if (scope === "global") {
|
|
290
|
+
return [{ rule: "Valid rule" }, { rule: "" }, { rule: "Another valid rule" }];
|
|
291
|
+
}
|
|
292
|
+
return [];
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const result = await findUserPreferencesByPath({
|
|
296
|
+
path: "/test",
|
|
297
|
+
scope: "document",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result).toEqual({
|
|
301
|
+
userPreferences: "Valid rule\n\n\n\nAnother valid rule",
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("should preserve rule order (global first, then scope)", async () => {
|
|
306
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
307
|
+
if (scope === "global") {
|
|
308
|
+
return [{ rule: "Global rule 1" }, { rule: "Global rule 2" }];
|
|
309
|
+
}
|
|
310
|
+
if (scope === "document") {
|
|
311
|
+
return [{ rule: "Document rule 1" }, { rule: "Document rule 2" }];
|
|
312
|
+
}
|
|
313
|
+
return [];
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const result = await findUserPreferencesByPath({
|
|
317
|
+
path: "/test",
|
|
318
|
+
scope: "document",
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(result).toEqual({
|
|
322
|
+
userPreferences: "Global rule 1\n\nGlobal rule 2\n\nDocument rule 1\n\nDocument rule 2",
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// PERFORMANCE AND EFFICIENCY TESTS
|
|
327
|
+
test("should call getActiveRulesForScope exactly twice", async () => {
|
|
328
|
+
await findUserPreferencesByPath({
|
|
329
|
+
path: "/performance/test",
|
|
330
|
+
scope: "translation",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledTimes(2);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("should handle large number of rules efficiently", async () => {
|
|
337
|
+
const manyRules = Array.from({ length: 100 }, (_, i) => ({
|
|
338
|
+
rule: `Rule number ${i + 1}`,
|
|
339
|
+
}));
|
|
340
|
+
|
|
341
|
+
getActiveRulesForScopeSpy.mockImplementation((scope) => {
|
|
342
|
+
if (scope === "global") {
|
|
343
|
+
return manyRules.slice(0, 50);
|
|
344
|
+
}
|
|
345
|
+
if (scope === "document") {
|
|
346
|
+
return manyRules.slice(50);
|
|
347
|
+
}
|
|
348
|
+
return [];
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const result = await findUserPreferencesByPath({
|
|
352
|
+
path: "/large/test",
|
|
353
|
+
scope: "document",
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(result.userPreferences.split("\n\n")).toHaveLength(100);
|
|
357
|
+
expect(result.userPreferences).toContain("Rule number 1");
|
|
358
|
+
expect(result.userPreferences).toContain("Rule number 100");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// PARAMETER VALIDATION TESTS
|
|
362
|
+
test("should work with various path formats", async () => {
|
|
363
|
+
const testPaths = [
|
|
364
|
+
"/simple",
|
|
365
|
+
"/nested/path",
|
|
366
|
+
"/deeply/nested/path/here",
|
|
367
|
+
"/with-dashes",
|
|
368
|
+
"/with_underscores",
|
|
369
|
+
"/with.dots",
|
|
370
|
+
"/api/v1/users/123",
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
for (const path of testPaths) {
|
|
374
|
+
await findUserPreferencesByPath({
|
|
375
|
+
path,
|
|
376
|
+
scope: "document",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(getActiveRulesForScopeSpy).toHaveBeenCalledWith("document", [path]);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { stringify } from "yaml";
|
|
3
|
+
import formatDocumentStructure from "../../../agents/utils/format-document-structure.mjs";
|
|
4
|
+
|
|
5
|
+
describe("format-document-structure", () => {
|
|
6
|
+
// BASIC FUNCTIONALITY TESTS
|
|
7
|
+
test("should format empty document structure", async () => {
|
|
8
|
+
const result = await formatDocumentStructure({
|
|
9
|
+
documentStructure: [],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const expectedYaml = stringify([], {
|
|
13
|
+
indent: 2,
|
|
14
|
+
lineWidth: 120,
|
|
15
|
+
minContentWidth: 20,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(result).toEqual({
|
|
19
|
+
documentStructureYaml: expectedYaml,
|
|
20
|
+
documentStructure: [],
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("should format single item document structure", async () => {
|
|
25
|
+
const documentStructure = [
|
|
26
|
+
{
|
|
27
|
+
title: "Getting Started",
|
|
28
|
+
path: "/getting-started",
|
|
29
|
+
parentId: null,
|
|
30
|
+
description: "Introduction to the platform",
|
|
31
|
+
extraField: "should be ignored",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
36
|
+
|
|
37
|
+
const expectedData = [
|
|
38
|
+
{
|
|
39
|
+
title: "Getting Started",
|
|
40
|
+
path: "/getting-started",
|
|
41
|
+
parentId: null,
|
|
42
|
+
description: "Introduction to the platform",
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const expectedYaml = stringify(expectedData, {
|
|
46
|
+
indent: 2,
|
|
47
|
+
lineWidth: 120,
|
|
48
|
+
minContentWidth: 20,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.documentStructure).toEqual(documentStructure);
|
|
52
|
+
expect(result.documentStructureYaml).toBe(expectedYaml);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should format multiple items document structure", async () => {
|
|
56
|
+
const documentStructure = [
|
|
57
|
+
{
|
|
58
|
+
title: "API Reference",
|
|
59
|
+
path: "/api",
|
|
60
|
+
parentId: null,
|
|
61
|
+
description: "Complete API documentation",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "Authentication",
|
|
65
|
+
path: "/api/auth",
|
|
66
|
+
parentId: "/api",
|
|
67
|
+
description: "Authentication endpoints",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "Users",
|
|
71
|
+
path: "/api/users",
|
|
72
|
+
parentId: "/api",
|
|
73
|
+
description: "User management endpoints",
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
78
|
+
|
|
79
|
+
const expectedData = [
|
|
80
|
+
{
|
|
81
|
+
title: "API Reference",
|
|
82
|
+
path: "/api",
|
|
83
|
+
parentId: null,
|
|
84
|
+
description: "Complete API documentation",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
title: "Authentication",
|
|
88
|
+
path: "/api/auth",
|
|
89
|
+
parentId: "/api",
|
|
90
|
+
description: "Authentication endpoints",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
title: "Users",
|
|
94
|
+
path: "/api/users",
|
|
95
|
+
parentId: "/api",
|
|
96
|
+
description: "User management endpoints",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
const expectedYaml = stringify(expectedData, {
|
|
100
|
+
indent: 2,
|
|
101
|
+
lineWidth: 120,
|
|
102
|
+
minContentWidth: 20,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(result.documentStructure).toEqual(documentStructure);
|
|
106
|
+
expect(result.documentStructureYaml).toBe(expectedYaml);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// FIELD EXTRACTION TESTS
|
|
110
|
+
test("should extract only required fields", async () => {
|
|
111
|
+
const documentStructure = [
|
|
112
|
+
{
|
|
113
|
+
title: "Test Document",
|
|
114
|
+
path: "/test",
|
|
115
|
+
parentId: "parent-123",
|
|
116
|
+
description: "Test description",
|
|
117
|
+
// Extra fields that should be filtered out
|
|
118
|
+
id: "doc-123",
|
|
119
|
+
content: "Document content",
|
|
120
|
+
metadata: { tags: ["test"] },
|
|
121
|
+
lastModified: "2024-01-01",
|
|
122
|
+
author: "John Doe",
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
127
|
+
|
|
128
|
+
const expectedData = [
|
|
129
|
+
{
|
|
130
|
+
title: "Test Document",
|
|
131
|
+
path: "/test",
|
|
132
|
+
parentId: "parent-123",
|
|
133
|
+
description: "Test description",
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
const expectedYaml = stringify(expectedData, {
|
|
137
|
+
indent: 2,
|
|
138
|
+
lineWidth: 120,
|
|
139
|
+
minContentWidth: 20,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.documentStructureYaml).toBe(expectedYaml);
|
|
143
|
+
// Verify extra fields are not in the YAML output
|
|
144
|
+
expect(result.documentStructureYaml).not.toContain("doc-123");
|
|
145
|
+
expect(result.documentStructureYaml).not.toContain("Document content");
|
|
146
|
+
expect(result.documentStructureYaml).not.toContain("John Doe");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("should handle items with missing optional fields", async () => {
|
|
150
|
+
const documentStructure = [
|
|
151
|
+
{
|
|
152
|
+
title: "Required Title",
|
|
153
|
+
path: "/required-path",
|
|
154
|
+
// parentId and description are missing
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
159
|
+
|
|
160
|
+
const expectedData = [
|
|
161
|
+
{
|
|
162
|
+
title: "Required Title",
|
|
163
|
+
path: "/required-path",
|
|
164
|
+
parentId: undefined,
|
|
165
|
+
description: undefined,
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
const expectedYaml = stringify(expectedData, {
|
|
169
|
+
indent: 2,
|
|
170
|
+
lineWidth: 120,
|
|
171
|
+
minContentWidth: 20,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(result.documentStructureYaml).toBe(expectedYaml);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("should preserve null and undefined values", async () => {
|
|
178
|
+
const documentStructure = [
|
|
179
|
+
{
|
|
180
|
+
title: "Test Item",
|
|
181
|
+
path: "/test",
|
|
182
|
+
parentId: null,
|
|
183
|
+
description: undefined,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
188
|
+
|
|
189
|
+
const expectedData = [
|
|
190
|
+
{
|
|
191
|
+
title: "Test Item",
|
|
192
|
+
path: "/test",
|
|
193
|
+
parentId: null,
|
|
194
|
+
description: undefined,
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
const expectedYaml = stringify(expectedData, {
|
|
198
|
+
indent: 2,
|
|
199
|
+
lineWidth: 120,
|
|
200
|
+
minContentWidth: 20,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result.documentStructureYaml).toBe(expectedYaml);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("should handle items with special characters", async () => {
|
|
207
|
+
const documentStructure = [
|
|
208
|
+
{
|
|
209
|
+
title: "Special Characters: @#$%^&*()",
|
|
210
|
+
path: "/special-chars-test",
|
|
211
|
+
parentId: null,
|
|
212
|
+
description: 'Testing with 中文, émojis 🚀, and "quotes"',
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
217
|
+
|
|
218
|
+
// Since YAML is mocked, just verify the function runs without error
|
|
219
|
+
// and returns a structure with the expected properties
|
|
220
|
+
expect(result.documentStructureYaml).toBeDefined();
|
|
221
|
+
expect(result.documentStructure).toEqual(documentStructure);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("should return both yaml string and original document structure", async () => {
|
|
225
|
+
const documentStructure = [
|
|
226
|
+
{
|
|
227
|
+
title: "Return Test",
|
|
228
|
+
path: "/return-test",
|
|
229
|
+
parentId: null,
|
|
230
|
+
description: "Testing return values",
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const result = await formatDocumentStructure({ documentStructure });
|
|
235
|
+
|
|
236
|
+
expect(result).toHaveProperty("documentStructureYaml");
|
|
237
|
+
expect(result).toHaveProperty("documentStructure");
|
|
238
|
+
expect(typeof result.documentStructureYaml).toBe("string");
|
|
239
|
+
expect(result.documentStructure).toBe(documentStructure); // Should be the same reference
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("should preserve original document structure unchanged", async () => {
|
|
243
|
+
const originalDocumentStructure = [
|
|
244
|
+
{
|
|
245
|
+
title: "Original",
|
|
246
|
+
path: "/original",
|
|
247
|
+
parentId: null,
|
|
248
|
+
description: "Original description",
|
|
249
|
+
extraField: "should remain",
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const result = await formatDocumentStructure({
|
|
254
|
+
documentStructure: originalDocumentStructure,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Original should be unchanged
|
|
258
|
+
expect(originalDocumentStructure[0]).toHaveProperty("extraField");
|
|
259
|
+
expect(originalDocumentStructure[0].extraField).toBe("should remain");
|
|
260
|
+
|
|
261
|
+
// Return value should reference the same object
|
|
262
|
+
expect(result.documentStructure).toBe(originalDocumentStructure);
|
|
263
|
+
});
|
|
264
|
+
});
|