swallowkit 1.0.0-beta.9 → 1.0.1
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.ja.md +87 -181
- package/README.md +87 -238
- package/dist/__tests__/fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +101 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/add-auth.d.ts +15 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +790 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +164 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +20 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +27 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
- package/dist/cli/commands/dev-seeds.js +188 -6
- package/dist/cli/commands/dev-seeds.js.map +1 -1
- package/dist/cli/commands/dev.d.ts +30 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +495 -129
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +69 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +788 -766
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +24 -3
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +408 -189
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +5 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +335 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +13 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +151 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- package/dist/core/operations/auth-operations.d.ts +61 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +272 -0
- package/dist/core/operations/auth-operations.js.map +1 -0
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +185 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/file-session.d.ts +56 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +203 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +37 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +248 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +51 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +187 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/agent-instructions.d.ts +29 -0
- package/dist/core/project/agent-instructions.d.ts.map +1 -0
- package/dist/core/project/agent-instructions.js +160 -0
- package/dist/core/project/agent-instructions.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/capabilities.d.ts +58 -0
- package/dist/core/project/capabilities.d.ts.map +1 -0
- package/dist/core/project/capabilities.js +135 -0
- package/dist/core/project/capabilities.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +190 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +274 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +299 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +63 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1774 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1029 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +4 -3
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +405 -134
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +229 -78
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +753 -0
- package/dist/core/scaffold/native-schema-generator.js.map +1 -0
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +152 -8
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/ui-generator.d.ts +17 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +174 -34
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +69 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +412 -0
- package/dist/core/verify/index.js.map +1 -0
- package/dist/machine/contracts.d.ts +24 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +14 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +42 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +566 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +425 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +105 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +7 -11
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +42 -17
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +21 -0
- package/dist/utils/python-uv.d.ts.map +1 -0
- package/dist/utils/python-uv.js +111 -0
- package/dist/utils/python-uv.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- package/package.json +15 -17
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
- package/src/__tests__/agent-instructions.test.ts +237 -0
- package/src/__tests__/agent-loop-phase34.test.ts +619 -0
- package/src/__tests__/auth.test.ts +770 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/config.test.ts +176 -3
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +459 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
- package/src/__tests__/dev-seeds.test.ts +124 -0
- package/src/__tests__/dev.test.ts +254 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/fixtures.ts +101 -0
- package/src/__tests__/functions-generator.test.ts +137 -1
- package/src/__tests__/init.test.ts +366 -2
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +371 -0
- package/src/__tests__/mcp.test.ts +253 -0
- package/src/__tests__/model-parser.test.ts +142 -0
- package/src/__tests__/multi-auth.test.ts +160 -0
- package/src/__tests__/nextjs-generator.test.ts +8 -0
- package/src/__tests__/package-manager.test.ts +59 -4
- package/src/__tests__/plan-apply.test.ts +499 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +287 -24
- package/src/__tests__/tsconfig.json +15 -0
- package/src/__tests__/ui-generator.test.ts +104 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +853 -0
- package/src/cli/commands/add-connector.ts +159 -0
- package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
- package/src/cli/commands/create-model.ts +21 -100
- package/src/cli/commands/dev-seeds.ts +278 -7
- package/src/cli/commands/dev.ts +607 -160
- package/src/cli/commands/init.ts +851 -786
- package/src/cli/commands/scaffold.ts +531 -236
- package/src/cli/index.ts +338 -60
- package/src/core/config.ts +145 -9
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/auth-operations.ts +360 -0
- package/src/core/operations/create-model.ts +191 -0
- package/src/core/operations/file-session.ts +222 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +258 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +77 -0
- package/src/core/operations/scaffold-plan.ts +215 -0
- package/src/core/project/agent-instructions.ts +167 -0
- package/src/core/project/agent-templates/AGENTS.md +504 -0
- package/src/core/project/agent-templates/CLAUDE.md +31 -0
- package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
- package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
- package/src/core/project/agent-templates/copilot-instructions.md +27 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/capabilities.ts +193 -0
- package/src/core/project/drift.ts +203 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/validation.ts +295 -0
- package/src/core/project/workflows.ts +295 -0
- package/src/core/scaffold/auth-generator.ts +1824 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +432 -134
- package/src/core/scaffold/model-parser.ts +244 -90
- package/src/core/scaffold/native-schema-generator.ts +904 -0
- package/src/core/scaffold/nextjs-generator.ts +174 -9
- package/src/core/scaffold/ui-generator.ts +191 -36
- package/src/core/verify/index.ts +468 -0
- package/src/machine/contracts.ts +33 -0
- package/src/machine/errors.ts +45 -0
- package/src/machine/index.ts +690 -0
- package/src/mcp/index.ts +423 -0
- package/src/types/index.ts +137 -0
- package/src/utils/package-manager.ts +41 -19
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
|
@@ -1,39 +1,302 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
-
import
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
import * as childProcess from "child_process";
|
|
6
|
+
import { generateCosmosContainer, generateUIComponents } from "../cli/commands/scaffold";
|
|
7
|
+
import {
|
|
8
|
+
NSWAG_CONSOLECORE_VERSION,
|
|
9
|
+
buildCSharpCodegenToolManifestSource,
|
|
10
|
+
buildPythonCodegenRequirementsSource,
|
|
11
|
+
generateLanguageSchemaArtifacts,
|
|
12
|
+
getCSharpNativeGeneratorArgs,
|
|
13
|
+
getCSharpSchemaModelPath,
|
|
14
|
+
getCSharpSchemaOptionPath,
|
|
15
|
+
getPythonNativeGeneratorArgs,
|
|
16
|
+
getPythonSchemaModelPath,
|
|
17
|
+
} from "../core/scaffold/native-schema-generator";
|
|
18
|
+
import { createBasicModelInfo } from "./fixtures";
|
|
3
19
|
|
|
4
|
-
describe("
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
describe("authenticated UI scaffold layouts", () => {
|
|
21
|
+
const originalCwd = process.cwd();
|
|
22
|
+
let tempDir: string;
|
|
23
|
+
const authOptions = {
|
|
24
|
+
authPolicy: { policy: "staffOnly" },
|
|
25
|
+
writeRoles: ["authenticated"],
|
|
26
|
+
authContextImport: "@/lib/auth/schemes/staff/auth-context",
|
|
27
|
+
};
|
|
8
28
|
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
tempDir = fs.mkdtempSync(path.join(originalCwd, ".tmp-auth-layout-"));
|
|
31
|
+
process.chdir(tempDir);
|
|
11
32
|
});
|
|
12
33
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
process.chdir(originalCwd);
|
|
36
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("writes a model-scoped provider layout and regenerates it stably", async () => {
|
|
40
|
+
const model = createBasicModelInfo({ name: "Product", schemaName: "productSchema" });
|
|
41
|
+
|
|
42
|
+
await generateUIComponents(model, "@fixture/shared", authOptions);
|
|
43
|
+
const layoutPath = path.join(tempDir, "app", "product", "layout.tsx");
|
|
44
|
+
const first = fs.readFileSync(layoutPath, "utf-8");
|
|
45
|
+
await generateUIComponents(model, "@fixture/shared", authOptions);
|
|
46
|
+
const second = fs.readFileSync(layoutPath, "utf-8");
|
|
47
|
+
|
|
48
|
+
expect(second).toBe(first);
|
|
49
|
+
expect(second).toContain("from '@/lib/auth/schemes/staff/auth-context'");
|
|
50
|
+
expect(second.match(/import \{ AuthProvider \}/g)).toHaveLength(1);
|
|
51
|
+
expect(second.match(/<AuthProvider>/g)).toHaveLength(1);
|
|
52
|
+
expect(fs.readFileSync(path.join(tempDir, "app", "product", "page.tsx"), "utf-8"))
|
|
53
|
+
.toContain("from '@/lib/auth/schemes/staff/auth-context'");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("does not create an auth layout for an unauthenticated model or affect another route", async () => {
|
|
57
|
+
await generateUIComponents(createBasicModelInfo({ name: "Product", schemaName: "productSchema" }), "@fixture/shared");
|
|
58
|
+
await generateUIComponents(createBasicModelInfo({ name: "Line", schemaName: "lineSchema" }), "@fixture/shared");
|
|
59
|
+
|
|
60
|
+
expect(fs.existsSync(path.join(tempDir, "app", "product", "layout.tsx"))).toBe(false);
|
|
61
|
+
expect(fs.existsSync(path.join(tempDir, "app", "line", "layout.tsx"))).toBe(false);
|
|
62
|
+
expect(fs.readFileSync(path.join(tempDir, "app", "line", "page.tsx"), "utf-8"))
|
|
63
|
+
.not.toContain("schemes/staff/auth-context");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("builds the generated authenticated CRUD routes with Next.js", async () => {
|
|
67
|
+
const model = createBasicModelInfo({ name: "Product", schemaName: "productSchema" });
|
|
68
|
+
await generateUIComponents(model, "@fixture/shared", authOptions);
|
|
69
|
+
|
|
70
|
+
writeFile(path.join(tempDir, "app", "layout.tsx"), `export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
71
|
+
return <html><body>{children}</body></html>;
|
|
72
|
+
}
|
|
73
|
+
`);
|
|
74
|
+
writeFile(path.join(tempDir, "lib", "auth", "schemes", "staff", "auth-context.tsx"), `'use client';
|
|
75
|
+
import { createContext, useContext } from 'react';
|
|
76
|
+
const AuthContext = createContext<{ user: { id: string }; loading: boolean; hasAnyRole: (roles: string[]) => boolean } | null>(null);
|
|
77
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
78
|
+
return <AuthContext.Provider value={{ user: { id: 'test' }, loading: false, hasAnyRole: () => true }}>{children}</AuthContext.Provider>;
|
|
79
|
+
}
|
|
80
|
+
export function useAuth() {
|
|
81
|
+
const value = useContext(AuthContext);
|
|
82
|
+
if (!value) throw new Error('useAuth must be used within an AuthProvider');
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
`);
|
|
86
|
+
writeFile(path.join(tempDir, "shared.ts"), `import { z } from 'zod/v4';
|
|
87
|
+
export const productSchema = z.object({
|
|
88
|
+
id: z.string(),
|
|
89
|
+
title: z.string(),
|
|
90
|
+
description: z.string().optional(),
|
|
91
|
+
completed: z.boolean(),
|
|
92
|
+
createdAt: z.string(),
|
|
93
|
+
updatedAt: z.string(),
|
|
94
|
+
});
|
|
95
|
+
`);
|
|
96
|
+
writeFile(path.join(tempDir, "package.json"), JSON.stringify({
|
|
97
|
+
private: true,
|
|
98
|
+
scripts: { build: "next build" },
|
|
99
|
+
}, null, 2));
|
|
100
|
+
writeFile(path.join(tempDir, "tsconfig.json"), JSON.stringify({
|
|
101
|
+
compilerOptions: {
|
|
102
|
+
target: "ES2020",
|
|
103
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
104
|
+
strict: true,
|
|
105
|
+
noEmit: true,
|
|
106
|
+
esModuleInterop: true,
|
|
107
|
+
module: "esnext",
|
|
108
|
+
moduleResolution: "bundler",
|
|
109
|
+
resolveJsonModule: true,
|
|
110
|
+
isolatedModules: true,
|
|
111
|
+
jsx: "preserve",
|
|
112
|
+
baseUrl: ".",
|
|
113
|
+
paths: { "@/*": ["./*"], "@fixture/shared": ["./shared.ts"] },
|
|
114
|
+
},
|
|
115
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
116
|
+
exclude: ["node_modules"],
|
|
117
|
+
}, null, 2));
|
|
118
|
+
|
|
119
|
+
const npmCommand = process.platform === "win32"
|
|
120
|
+
? { file: process.env.ComSpec ?? "cmd.exe", args: ["/d", "/s", "/c", "npm run build"] }
|
|
121
|
+
: { file: "npm", args: ["run", "build"] };
|
|
122
|
+
expect(() => childProcess.execFileSync(npmCommand.file, npmCommand.args, {
|
|
123
|
+
cwd: tempDir,
|
|
124
|
+
env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" },
|
|
125
|
+
stdio: "pipe",
|
|
126
|
+
timeout: 120_000,
|
|
127
|
+
})).not.toThrow();
|
|
128
|
+
}, 150_000);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
jest.mock("child_process", () => {
|
|
132
|
+
const actual = jest.requireActual<typeof import("child_process")>("child_process");
|
|
133
|
+
return {
|
|
134
|
+
...actual,
|
|
135
|
+
spawn: jest.fn(),
|
|
136
|
+
spawnSync: jest.fn(),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
function writeFile(filePath: string, content: string): void {
|
|
141
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
142
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function mockSuccessfulSpawn() {
|
|
146
|
+
const spawnMock = childProcess.spawn as unknown as any;
|
|
147
|
+
spawnMock.mockImplementation(() => {
|
|
148
|
+
const child = new EventEmitter() as childProcess.ChildProcess;
|
|
149
|
+
process.nextTick(() => child.emit("close", 0));
|
|
150
|
+
return child;
|
|
151
|
+
});
|
|
152
|
+
return spawnMock;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
describe("native schema generators", () => {
|
|
156
|
+
it("writes a dotnet tool manifest for NSwag", () => {
|
|
157
|
+
const manifest = JSON.parse(buildCSharpCodegenToolManifestSource());
|
|
158
|
+
|
|
159
|
+
expect(manifest.version).toBe(1);
|
|
160
|
+
expect(manifest.tools["nswag.consolecore"].version).toBe(NSWAG_CONSOLECORE_VERSION);
|
|
161
|
+
expect(manifest.tools["nswag.consolecore"].commands).toEqual(["nswag"]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("pins python schema generation requirements without Java", () => {
|
|
165
|
+
expect(buildPythonCodegenRequirementsSource()).toContain("datamodel-code-generator");
|
|
166
|
+
expect(buildPythonCodegenRequirementsSource()).not.toContain("openapi-generator");
|
|
167
|
+
});
|
|
16
168
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
169
|
+
it("builds NSwag arguments for model-only C# generation", () => {
|
|
170
|
+
const outputPath = path.join(os.tmpdir(), "swallowkit-csharp-codegen-example", "Contracts.cs");
|
|
171
|
+
const args = getCSharpNativeGeneratorArgs("C:\\temp\\todo.openapi.json", outputPath);
|
|
172
|
+
|
|
173
|
+
expect(args.slice(0, 4)).toEqual(["tool", "run", "nswag", "openapi2csclient"]);
|
|
174
|
+
expect(args).toContain("/GenerateClientClasses:false");
|
|
175
|
+
expect(args).toContain("/GenerateDtoTypes:true");
|
|
176
|
+
expect(args).toContain("/GenerateNullableReferenceTypes:true");
|
|
177
|
+
expect(args).toContain(`/output:${outputPath}`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("builds datamodel-code-generator arguments for python assets", () => {
|
|
181
|
+
const outputPath = path.join(os.tmpdir(), "swallowkit-python-codegen-example", "models.py");
|
|
182
|
+
const args = getPythonNativeGeneratorArgs("C:\\temp\\todo.openapi.json", outputPath);
|
|
183
|
+
|
|
184
|
+
expect(args).toEqual(
|
|
185
|
+
expect.arrayContaining([
|
|
186
|
+
"-m",
|
|
187
|
+
"datamodel_code_generator",
|
|
188
|
+
"--input-file-type",
|
|
189
|
+
"openapi",
|
|
190
|
+
"--output-model-type",
|
|
191
|
+
"pydantic_v2.BaseModel",
|
|
192
|
+
"--disable-timestamp",
|
|
193
|
+
outputPath,
|
|
194
|
+
])
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("keeps generated asset paths under functions/generated", () => {
|
|
199
|
+
expect(getCSharpSchemaModelPath("C:\\temp\\generated\\csharp-models", "Product")).toBe(
|
|
200
|
+
path.join("C:\\temp\\generated\\csharp-models", "src", "SwallowKitBackendModels", "Model", "Product.cs")
|
|
201
|
+
);
|
|
202
|
+
expect(getCSharpSchemaOptionPath("C:\\temp\\generated\\csharp-models")).toBe(
|
|
203
|
+
path.join("C:\\temp\\generated\\csharp-models", "src", "SwallowKitBackendModels", "Client", "Option.cs")
|
|
204
|
+
);
|
|
205
|
+
expect(getPythonSchemaModelPath("C:\\temp\\generated\\python-models", "Product")).toBe(
|
|
206
|
+
path.join("C:\\temp\\generated\\python-models", "backend_models", "models", "product.py")
|
|
20
207
|
);
|
|
21
208
|
});
|
|
209
|
+
describe("generateCosmosContainer", () => {
|
|
210
|
+
const originalCwd = process.cwd();
|
|
211
|
+
let tempDir: string;
|
|
22
212
|
|
|
23
|
-
|
|
24
|
-
|
|
213
|
+
beforeEach(() => {
|
|
214
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-cosmos-container-"));
|
|
215
|
+
process.chdir(tempDir);
|
|
216
|
+
fs.mkdirSync(path.join(tempDir, "infra", "containers"), { recursive: true });
|
|
217
|
+
fs.writeFileSync(path.join(tempDir, "infra", "main.bicep"), "module cosmosDbServerless 'modules/cosmosdb-serverless.bicep' = if (cosmosDbMode == 'serverless') {\n name: 'cosmosDb'\n}\n");
|
|
218
|
+
});
|
|
25
219
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
path.join("C:\\temp\\generated\\csharp-models", "src", "SwallowKitBackendModels", "Extensions"),
|
|
30
|
-
]);
|
|
220
|
+
afterEach(() => {
|
|
221
|
+
process.chdir(originalCwd);
|
|
222
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
31
223
|
});
|
|
32
224
|
|
|
33
|
-
it("
|
|
34
|
-
|
|
225
|
+
it("generates container templates without container-level throughput settings", async () => {
|
|
226
|
+
await generateCosmosContainer({ name: "SurveyResponse", partitionKey: "/id" });
|
|
227
|
+
|
|
228
|
+
const containerFilePath = path.join(tempDir, "infra", "containers", "survey-response-container.bicep");
|
|
229
|
+
const containerContent = fs.readFileSync(containerFilePath, "utf-8");
|
|
230
|
+
const mainContent = fs.readFileSync(path.join(tempDir, "infra", "main.bicep"), "utf-8");
|
|
231
|
+
|
|
232
|
+
expect(containerContent).toContain("resource container");
|
|
233
|
+
expect(containerContent).not.toContain("throughput");
|
|
234
|
+
expect(containerContent).not.toContain("options:");
|
|
235
|
+
expect(mainContent).toContain("module surveyResponseContainer 'containers/survey-response-container.bicep'");
|
|
236
|
+
expect(mainContent).not.toContain("dependsOn");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe("non-destructive native schema generation", () => {
|
|
240
|
+
const originalCwd = process.cwd();
|
|
241
|
+
let tempDir: string;
|
|
242
|
+
let spawnSyncMock: any;
|
|
243
|
+
let spawnMock: any;
|
|
244
|
+
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-native-schema-"));
|
|
247
|
+
process.chdir(tempDir);
|
|
248
|
+
fs.mkdirSync(path.join(tempDir, "functions"), { recursive: true });
|
|
249
|
+
spawnSyncMock = childProcess.spawnSync as unknown as any;
|
|
250
|
+
spawnSyncMock.mockReturnValue({ status: 0 });
|
|
251
|
+
spawnMock = mockSuccessfulSpawn();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
afterEach(() => {
|
|
255
|
+
spawnMock.mockReset();
|
|
256
|
+
spawnSyncMock.mockReset();
|
|
257
|
+
process.chdir(originalCwd);
|
|
258
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("preserves existing C# generated directory contents while writing only requested model files", async () => {
|
|
262
|
+
const outputDir = path.join(tempDir, "functions", "generated", "csharp-models");
|
|
263
|
+
writeFile(path.join(outputDir, "README.md"), "keep me\n");
|
|
264
|
+
writeFile(path.join(outputDir, "SwallowKitBackendModels.csproj"), "<Project />\n");
|
|
265
|
+
writeFile(path.join(outputDir, "src", "SwallowKitBackendModels", "Model", "Member.cs"), "// existing member\n");
|
|
266
|
+
|
|
267
|
+
const estimate = createBasicModelInfo({ name: "Estimate", filePath: path.join(tempDir, "shared", "models", "estimate.ts") });
|
|
268
|
+
|
|
269
|
+
await generateLanguageSchemaArtifacts([estimate], estimate, "functions", "csharp");
|
|
270
|
+
|
|
271
|
+
expect(fs.readFileSync(path.join(outputDir, "README.md"), "utf-8")).toBe("keep me\n");
|
|
272
|
+
expect(fs.existsSync(path.join(outputDir, "SwallowKitBackendModels.csproj"))).toBe(true);
|
|
273
|
+
expect(fs.readFileSync(getCSharpSchemaModelPath(outputDir, "Member"), "utf-8")).toBe("// existing member\n");
|
|
274
|
+
expect(fs.existsSync(getCSharpSchemaModelPath(outputDir, "Todo"))).toBe(false);
|
|
275
|
+
expect(fs.existsSync(getCSharpSchemaModelPath(outputDir, "Estimate"))).toBe(true);
|
|
276
|
+
expect(fs.existsSync(getCSharpSchemaOptionPath(outputDir))).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("preserves existing Python generated directory contents while appending requested model exports", async () => {
|
|
280
|
+
const outputDir = path.join(tempDir, "functions", "generated", "python-models");
|
|
281
|
+
writeFile(path.join(outputDir, "README.md"), "keep python\n");
|
|
282
|
+
writeFile(path.join(outputDir, "backend_models", "models", "member.py"), "# existing member\n");
|
|
283
|
+
writeFile(path.join(outputDir, "backend_models", "__init__.py"), "from .models.member import Member\n");
|
|
284
|
+
writeFile(path.join(outputDir, "backend_models", "models", "__init__.py"), "from .member import Member\n");
|
|
285
|
+
|
|
286
|
+
const estimate = createBasicModelInfo({ name: "Estimate", filePath: path.join(tempDir, "shared", "models", "estimate.ts") });
|
|
287
|
+
|
|
288
|
+
await generateLanguageSchemaArtifacts([estimate], estimate, "functions", "python");
|
|
35
289
|
|
|
36
|
-
|
|
37
|
-
|
|
290
|
+
expect(fs.readFileSync(path.join(outputDir, "README.md"), "utf-8")).toBe("keep python\n");
|
|
291
|
+
expect(fs.readFileSync(path.join(outputDir, "backend_models", "models", "member.py"), "utf-8")).toBe("# existing member\n");
|
|
292
|
+
expect(fs.existsSync(getPythonSchemaModelPath(outputDir, "Todo"))).toBe(false);
|
|
293
|
+
expect(fs.existsSync(getPythonSchemaModelPath(outputDir, "Estimate"))).toBe(true);
|
|
294
|
+
const packageInit = fs.readFileSync(path.join(outputDir, "backend_models", "__init__.py"), "utf-8");
|
|
295
|
+
const modelsInit = fs.readFileSync(path.join(outputDir, "backend_models", "models", "__init__.py"), "utf-8");
|
|
296
|
+
expect(packageInit).toContain("from .models.member import Member");
|
|
297
|
+
expect(packageInit).toContain("from .models.estimate import Estimate");
|
|
298
|
+
expect(modelsInit).toContain("from .member import Member");
|
|
299
|
+
expect(modelsInit).toContain("from .estimate import Estimate");
|
|
300
|
+
});
|
|
38
301
|
});
|
|
39
302
|
});
|
|
@@ -1,16 +1,56 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
1
4
|
import {
|
|
2
5
|
generateListPage,
|
|
3
6
|
generateDetailPage,
|
|
4
7
|
generateFormComponent,
|
|
5
8
|
generateNewPage,
|
|
6
9
|
generateEditPage,
|
|
10
|
+
generateAuthLayout,
|
|
7
11
|
} from "../core/scaffold/ui-generator";
|
|
8
12
|
import {
|
|
9
13
|
createBasicModelInfo,
|
|
10
14
|
createModelInfoWithForeignKey,
|
|
15
|
+
createModelInfoWithNullableForeignKeys,
|
|
11
16
|
createModelInfoWithEnum,
|
|
12
17
|
} from "./fixtures";
|
|
13
18
|
|
|
19
|
+
const namedAuthOptions = {
|
|
20
|
+
authPolicy: { policy: "staffOnly" },
|
|
21
|
+
writeRoles: ["authenticated"],
|
|
22
|
+
authContextImport: "@/lib/auth/schemes/staff/auth-context",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("generateAuthLayout", () => {
|
|
26
|
+
it("uses the same resolved auth context as authenticated CRUD pages", () => {
|
|
27
|
+
const model = createBasicModelInfo({ name: "Product" });
|
|
28
|
+
const layout = generateAuthLayout(model, namedAuthOptions);
|
|
29
|
+
const listPage = generateListPage(model, "@myapp/shared", namedAuthOptions);
|
|
30
|
+
const newPage = generateNewPage(model, namedAuthOptions);
|
|
31
|
+
|
|
32
|
+
expect(layout).toContain("import { AuthProvider } from '@/lib/auth/schemes/staff/auth-context';");
|
|
33
|
+
expect(layout).toContain("<AuthProvider>{children}</AuthProvider>");
|
|
34
|
+
expect(layout).toContain("function ProductLayout");
|
|
35
|
+
expect(listPage).toContain("import { useAuth } from '@/lib/auth/schemes/staff/auth-context';");
|
|
36
|
+
expect(newPage).toContain("import { useAuth } from '@/lib/auth/schemes/staff/auth-context';");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not generate an auth layout without auth options", () => {
|
|
40
|
+
expect(generateAuthLayout(createBasicModelInfo({ name: "Product" }))).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("is deterministic and does not duplicate providers or imports", () => {
|
|
44
|
+
const model = createBasicModelInfo({ name: "Product" });
|
|
45
|
+
const first = generateAuthLayout(model, namedAuthOptions);
|
|
46
|
+
const second = generateAuthLayout(model, namedAuthOptions);
|
|
47
|
+
|
|
48
|
+
expect(second).toBe(first);
|
|
49
|
+
expect(first?.match(/import \{ AuthProvider \}/g)).toHaveLength(1);
|
|
50
|
+
expect(first?.match(/<AuthProvider>/g)).toHaveLength(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
14
54
|
describe("generateListPage", () => {
|
|
15
55
|
it("generates list page for basic model (snapshot)", () => {
|
|
16
56
|
const model = createBasicModelInfo();
|
|
@@ -48,6 +88,15 @@ describe("generateListPage", () => {
|
|
|
48
88
|
const code = generateListPage(model, "@myapp/shared");
|
|
49
89
|
expect(code).toContain("categoryId");
|
|
50
90
|
});
|
|
91
|
+
|
|
92
|
+
it("guards optional and nullable foreign key map lookups", () => {
|
|
93
|
+
const code = generateListPage(createModelInfoWithNullableForeignKeys(), "@myapp/shared");
|
|
94
|
+
|
|
95
|
+
expect(code).toContain("requiredUserMap[item.requiredUserId] || item.requiredUserId");
|
|
96
|
+
expect(code).toContain("item.optionalUserId ? optionalUserMap[item.optionalUserId] || item.optionalUserId : '-'");
|
|
97
|
+
expect(code).toContain("item.nullableUserId ? nullableUserMap[item.nullableUserId] || item.nullableUserId : '-'");
|
|
98
|
+
expect(code).not.toContain("requiredUserId ?");
|
|
99
|
+
});
|
|
51
100
|
});
|
|
52
101
|
|
|
53
102
|
describe("generateDetailPage", () => {
|
|
@@ -68,6 +117,61 @@ describe("generateDetailPage", () => {
|
|
|
68
117
|
const code = generateDetailPage(model, "@myapp/shared");
|
|
69
118
|
expect(code).toContain("DELETE");
|
|
70
119
|
});
|
|
120
|
+
|
|
121
|
+
it("guards optional and nullable foreign key map lookups", () => {
|
|
122
|
+
const code = generateDetailPage(createModelInfoWithNullableForeignKeys(), "@myapp/shared");
|
|
123
|
+
|
|
124
|
+
expect(code).toContain("requiredUserMap[coupon.requiredUserId] || coupon.requiredUserId");
|
|
125
|
+
expect(code).toContain("coupon.optionalUserId ? optionalUserMap[coupon.optionalUserId] || coupon.optionalUserId : '-'");
|
|
126
|
+
expect(code).toContain("coupon.nullableUserId ? nullableUserMap[coupon.nullableUserId] || coupon.nullableUserId : '-'");
|
|
127
|
+
expect(code).not.toContain("requiredUserId ?");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("generates list and detail pages that pass strict TypeScript checking", () => {
|
|
131
|
+
const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-ui-generator-"));
|
|
132
|
+
const model = createModelInfoWithNullableForeignKeys();
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
fs.writeFileSync(path.join(tempDir, "list.tsx"), generateListPage(model, "@myapp/shared"));
|
|
136
|
+
fs.writeFileSync(path.join(tempDir, "detail.tsx"), generateDetailPage(model, "@myapp/shared"));
|
|
137
|
+
fs.writeFileSync(
|
|
138
|
+
path.join(tempDir, "shared.ts"),
|
|
139
|
+
`import { z } from 'zod/v4';
|
|
140
|
+
export const couponSchema = z.object({
|
|
141
|
+
id: z.string(),
|
|
142
|
+
requiredUserId: z.string(),
|
|
143
|
+
optionalUserId: z.string().optional(),
|
|
144
|
+
nullableUserId: z.string().nullable(),
|
|
145
|
+
});
|
|
146
|
+
`
|
|
147
|
+
);
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
path.join(tempDir, "tsconfig.json"),
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
compilerOptions: {
|
|
152
|
+
target: "ES2020",
|
|
153
|
+
module: "commonjs",
|
|
154
|
+
moduleResolution: "node",
|
|
155
|
+
jsx: "react-jsx",
|
|
156
|
+
strict: true,
|
|
157
|
+
esModuleInterop: true,
|
|
158
|
+
skipLibCheck: true,
|
|
159
|
+
noEmit: true,
|
|
160
|
+
baseUrl: ".",
|
|
161
|
+
paths: { "@myapp/shared": ["./shared.ts"] },
|
|
162
|
+
},
|
|
163
|
+
include: ["*.ts", "*.tsx"],
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(() => execFileSync(process.execPath, [require.resolve("typescript/bin/tsc"), "-p", tempDir], {
|
|
168
|
+
cwd: process.cwd(),
|
|
169
|
+
stdio: "pipe",
|
|
170
|
+
})).not.toThrow();
|
|
171
|
+
} finally {
|
|
172
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
71
175
|
});
|
|
72
176
|
|
|
73
177
|
describe("generateFormComponent", () => {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod モックデータジェネレーターのテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { generateMockDocument, generateMockDocuments } from "../core/mock/zod-mock-generator";
|
|
6
|
+
import {
|
|
7
|
+
createBasicModelInfo,
|
|
8
|
+
createRdbConnectorModelInfo,
|
|
9
|
+
createApiConnectorModelInfo,
|
|
10
|
+
createModelInfoWithEnum,
|
|
11
|
+
} from "./fixtures";
|
|
12
|
+
|
|
13
|
+
describe("generateMockDocument", () => {
|
|
14
|
+
it("generates a document with all model fields", () => {
|
|
15
|
+
const model = createBasicModelInfo();
|
|
16
|
+
const doc = generateMockDocument(model, 1);
|
|
17
|
+
|
|
18
|
+
expect(doc).toHaveProperty("id");
|
|
19
|
+
expect(doc).toHaveProperty("title");
|
|
20
|
+
expect(doc).toHaveProperty("description");
|
|
21
|
+
expect(doc).toHaveProperty("completed");
|
|
22
|
+
expect(doc).toHaveProperty("createdAt");
|
|
23
|
+
expect(doc).toHaveProperty("updatedAt");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("generates id using model name and index", () => {
|
|
27
|
+
const model = createBasicModelInfo();
|
|
28
|
+
const doc = generateMockDocument(model, 1);
|
|
29
|
+
expect(doc.id).toBe("todo-001");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("generates different values for different indices", () => {
|
|
33
|
+
const model = createBasicModelInfo();
|
|
34
|
+
const doc1 = generateMockDocument(model, 1);
|
|
35
|
+
const doc2 = generateMockDocument(model, 2);
|
|
36
|
+
expect(doc1.id).not.toBe(doc2.id);
|
|
37
|
+
expect(doc1.title).not.toBe(doc2.title);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("generates boolean fields with alternating values", () => {
|
|
41
|
+
const model = createBasicModelInfo();
|
|
42
|
+
const doc1 = generateMockDocument(model, 1);
|
|
43
|
+
const doc2 = generateMockDocument(model, 2);
|
|
44
|
+
expect(typeof doc1.completed).toBe("boolean");
|
|
45
|
+
expect(doc1.completed).not.toBe(doc2.completed);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("generates ISO date strings for *At fields", () => {
|
|
49
|
+
const model = createBasicModelInfo();
|
|
50
|
+
const doc = generateMockDocument(model, 1);
|
|
51
|
+
expect(typeof doc.createdAt).toBe("string");
|
|
52
|
+
// Should be parseable as ISO date
|
|
53
|
+
expect(new Date(doc.createdAt as string).toISOString()).toBe(doc.createdAt);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("generates string values for title fields", () => {
|
|
57
|
+
const model = createBasicModelInfo();
|
|
58
|
+
const doc = generateMockDocument(model, 1);
|
|
59
|
+
expect(typeof doc.title).toBe("string");
|
|
60
|
+
expect(doc.title).toContain("1");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("handles optional string fields", () => {
|
|
64
|
+
const model = createBasicModelInfo();
|
|
65
|
+
const doc = generateMockDocument(model, 1);
|
|
66
|
+
// description is optional but still gets a value for mock data
|
|
67
|
+
expect(typeof doc.description).toBe("string");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("generates email-like values for email fields", () => {
|
|
71
|
+
const model = createRdbConnectorModelInfo();
|
|
72
|
+
const doc = generateMockDocument(model, 1);
|
|
73
|
+
expect(doc.email).toContain("@example.com");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("generates department values for department fields", () => {
|
|
77
|
+
const model = createRdbConnectorModelInfo();
|
|
78
|
+
const doc = generateMockDocument(model, 1);
|
|
79
|
+
expect(typeof doc.department).toBe("string");
|
|
80
|
+
// Should be one of the department names
|
|
81
|
+
expect(["Engineering", "Sales", "Marketing", "Support", "HR"]).toContain(doc.department);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("cycles enum values across indices", () => {
|
|
85
|
+
const model = createModelInfoWithEnum();
|
|
86
|
+
const doc1 = generateMockDocument(model, 1);
|
|
87
|
+
const doc2 = generateMockDocument(model, 2);
|
|
88
|
+
const doc3 = generateMockDocument(model, 3);
|
|
89
|
+
expect(doc1.status).toBe("open");
|
|
90
|
+
expect(doc2.status).toBe("in_progress");
|
|
91
|
+
expect(doc3.status).toBe("closed");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("generates foreign key references for *Id fields", () => {
|
|
95
|
+
const model = createApiConnectorModelInfo();
|
|
96
|
+
const doc = generateMockDocument(model, 1);
|
|
97
|
+
expect(typeof doc.projectId).toBe("string");
|
|
98
|
+
expect(doc.projectId).toContain("project-");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("generateMockDocuments", () => {
|
|
103
|
+
it("generates the specified number of documents", () => {
|
|
104
|
+
const model = createBasicModelInfo();
|
|
105
|
+
const docs = generateMockDocuments(model, 3);
|
|
106
|
+
expect(docs).toHaveLength(3);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("defaults to 5 documents", () => {
|
|
110
|
+
const model = createBasicModelInfo();
|
|
111
|
+
const docs = generateMockDocuments(model);
|
|
112
|
+
expect(docs).toHaveLength(5);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("generates unique ids for each document", () => {
|
|
116
|
+
const model = createBasicModelInfo();
|
|
117
|
+
const docs = generateMockDocuments(model, 5);
|
|
118
|
+
const ids = docs.map((d) => d.id);
|
|
119
|
+
const uniqueIds = new Set(ids);
|
|
120
|
+
expect(uniqueIds.size).toBe(5);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("works with connector models", () => {
|
|
124
|
+
const model = createRdbConnectorModelInfo();
|
|
125
|
+
const docs = generateMockDocuments(model, 3);
|
|
126
|
+
expect(docs).toHaveLength(3);
|
|
127
|
+
// All should have email fields with @example.com
|
|
128
|
+
for (const doc of docs) {
|
|
129
|
+
expect(doc.email).toContain("@example.com");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|