@tailor-platform/create-sdk 1.46.0 → 1.47.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @tailor-platform/create-sdk
2
2
 
3
+ ## 1.47.1
4
+
5
+ ## 1.47.0
6
+
7
+ ### Patch Changes
8
+
9
+ - [#1115](https://github.com/tailor-platform/sdk/pull/1115) [`8dd619e`](https://github.com/tailor-platform/sdk/commit/8dd619e9c58f4662b117bbd968ecf9528d688fe4) Thanks [@toiroakr](https://github.com/toiroakr)! - Add `@tailor-platform/sdk/vitest` (beta) — a Vitest plugin and environment that emulates the Tailor Platform function runtime locally. Catches `node:*` imports and Node.js globals usage that would fail at deploy time, and provides mock control objects (`tailordbMock`, `workflowMock`, `secretmanagerMock`, `authconnectionMock`, `idpMock`, `fileMock`, `iconvMock`) for all platform APIs with response configuration and call recording.
10
+
11
+ Revamp `packages/sdk/docs/testing.md` into a 2-layer model (Unit Tests / E2E Tests). The previous structure split Unit, Bundled, and Workflow tests across overlapping sections and contained broken vitest imports and references to a non-existent `--template testing`. The new docs cover testing resolvers (simple, with TailorDB mocks, with DI, and with wait points) and workflow jobs (simple, with `triggerJobFunction` mocks, with wait-point mocks, and full-workflow integration), all anchored on the actual `resolver` and `workflow` templates.
12
+
13
+ Mark `createImportMain` and `setupInvokerMock` from `@tailor-platform/sdk/test` as `@deprecated`. `createImportMain` is an SDK-internal helper for verifying bundled output; applications should test their TypeScript source directly (unit) and verify deployed behavior via E2E. `setupInvokerMock` is superseded by the `tailor-runtime` Vitest environment, where bundled tests can drive the invoker via `vi.spyOn(globalThis.tailor.context, "getInvoker").mockReturnValue(...)` and unit tests can pass `invoker` directly to `.body()`. Both exports remain in place for now to avoid a breaking change and will be removed in a future release.
14
+
15
+ Remove the broken `tests/bundled.test.ts` from the `resolver` and `workflow` templates along with the related `bundled` vitest project and `test:bundled` / `test:bundled:prepare` scripts. These tests were not exercised by CI and had drifted out of sync with the SDK, producing failures on a fresh scaffold.
16
+
17
+ Fix a broken anchor in `docs/services/workflow.md` that pointed at the removed `#testing-wait-points` heading; it now links to `../testing.md#jobs-that-wait-on-approval` to match the new testing docs structure.
18
+
3
19
  ## 1.46.0
4
20
 
5
21
  ## 1.45.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/create-sdk",
3
- "version": "1.46.0",
3
+ "version": "1.47.1",
4
4
  "description": "A CLI tool to quickly create a new Tailor Platform SDK project",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,7 +8,6 @@
8
8
  "url": "git+https://github.com/tailor-platform/sdk.git",
9
9
  "directory": "packages/create-sdk"
10
10
  },
11
- "bin": "dist/index.js",
12
11
  "files": [
13
12
  "CHANGELOG.md",
14
13
  "dist",
@@ -18,7 +17,7 @@
18
17
  ],
19
18
  "type": "module",
20
19
  "dependencies": {
21
- "@clack/prompts": "1.2.0",
20
+ "@clack/prompts": "1.3.0",
22
21
  "execa": "9.6.1",
23
22
  "picocolors": "1.1.1",
24
23
  "pkg-types": "2.3.1",
@@ -27,13 +26,13 @@
27
26
  },
28
27
  "devDependencies": {
29
28
  "@eslint/js": "10.0.1",
30
- "@types/node": "24.12.2",
29
+ "@types/node": "24.12.3",
31
30
  "eslint": "10.3.0",
32
31
  "eslint-plugin-oxlint": "1.61.0",
33
32
  "oxlint": "1.61.0",
34
- "tsdown": "0.21.10",
33
+ "tsdown": "0.22.0",
35
34
  "typescript": "5.9.3",
36
- "typescript-eslint": "8.59.1"
35
+ "typescript-eslint": "8.59.2"
37
36
  },
38
37
  "scripts": {
39
38
  "build": "tsdown",
@@ -42,5 +41,8 @@
42
41
  "typecheck": "tsc --noEmit",
43
42
  "prepublish": "node scripts/prepare-templates.js && pnpm run build",
44
43
  "publint": "publint --strict"
44
+ },
45
+ "bin": {
46
+ "create-sdk": "dist/index.js"
45
47
  }
46
48
  }
@@ -15,15 +15,15 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@eslint/js": "10.0.1",
18
- "@tailor-platform/sdk": "1.46.0",
19
- "@types/node": "24.12.2",
18
+ "@tailor-platform/sdk": "1.47.1",
19
+ "@types/node": "24.12.3",
20
20
  "eslint": "10.3.0",
21
21
  "eslint-plugin-oxlint": "1.61.0",
22
22
  "oxfmt": "0.46.0",
23
23
  "oxlint": "1.61.0",
24
24
  "oxlint-tsgolint": "0.22.1",
25
25
  "typescript": "5.9.3",
26
- "typescript-eslint": "8.59.1",
26
+ "typescript-eslint": "8.59.2",
27
27
  "vitest": "4.1.5"
28
28
  }
29
29
  }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import onUserCreated from "./onUserCreated";
3
+ import * as shared from "./shared";
4
+
5
+ describe("onUserCreated executor", () => {
6
+ test("creates an audit log with the new user's name and email", async () => {
7
+ const createAuditLog = vi.spyOn(shared, "createAuditLog").mockResolvedValue(undefined);
8
+
9
+ if (onUserCreated.operation.kind !== "function") {
10
+ throw new Error("expected function operation");
11
+ }
12
+ await onUserCreated.operation.body({
13
+ newRecord: {
14
+ id: "user-1",
15
+ name: "Alice",
16
+ email: "alice@example.com",
17
+ role: "ADMIN",
18
+ createdAt: "2025-01-01T00:00:00Z",
19
+ updatedAt: "2025-01-01T00:00:00Z",
20
+ },
21
+ });
22
+
23
+ expect(createAuditLog).toHaveBeenCalledExactlyOnceWith({
24
+ action: "USER_CREATED",
25
+ entityType: "User",
26
+ entityId: "user-1",
27
+ message: "Admin user created: Alice (alice@example.com)",
28
+ });
29
+ });
30
+ });
@@ -1,29 +1,13 @@
1
- import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest";
1
+ import { tailordbMock } from "@tailor-platform/sdk/vitest";
2
+ import { beforeEach, describe, expect, test } from "vitest";
2
3
  import { createAuditLog } from "./shared";
3
4
 
4
5
  describe("createAuditLog", () => {
5
- const mockQueryObject = vi.fn();
6
- beforeAll(() => {
7
- vi.stubGlobal("tailordb", {
8
- Client: vi.fn(
9
- class {
10
- connect = vi.fn();
11
- end = vi.fn();
12
- queryObject = mockQueryObject;
13
- },
14
- ),
15
- });
16
- });
17
- afterAll(() => {
18
- vi.unstubAllGlobals();
19
- });
20
- afterEach(() => {
21
- mockQueryObject.mockReset();
6
+ beforeEach(() => {
7
+ tailordbMock.reset();
22
8
  });
23
9
 
24
10
  test("inserts audit log record", async () => {
25
- mockQueryObject.mockResolvedValueOnce({});
26
-
27
11
  await createAuditLog({
28
12
  action: "USER_CREATED",
29
13
  entityType: "User",
@@ -31,6 +15,6 @@ describe("createAuditLog", () => {
31
15
  message: "Test audit log",
32
16
  });
33
17
 
34
- expect(mockQueryObject).toHaveBeenCalledTimes(1);
18
+ expect(tailordbMock.executedQueries).toHaveLength(1);
35
19
  });
36
20
  });
@@ -1,12 +1,16 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { tailorRuntime } from "@tailor-platform/sdk/vitest";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [tailorRuntime()],
4
6
  test: {
5
7
  watch: false,
6
8
  projects: [
7
9
  {
10
+ extends: true,
8
11
  test: {
9
12
  name: { label: "unit", color: "blue" },
13
+ environment: "tailor-runtime",
10
14
  include: ["src/**/*.test.ts"],
11
15
  },
12
16
  },
@@ -15,15 +15,15 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@eslint/js": "10.0.1",
18
- "@tailor-platform/sdk": "1.46.0",
19
- "@types/node": "24.12.2",
18
+ "@tailor-platform/sdk": "1.47.1",
19
+ "@types/node": "24.12.3",
20
20
  "eslint": "10.3.0",
21
21
  "eslint-plugin-oxlint": "1.61.0",
22
22
  "oxfmt": "0.46.0",
23
23
  "oxlint": "1.61.0",
24
24
  "oxlint-tsgolint": "0.22.1",
25
25
  "typescript": "5.9.3",
26
- "typescript-eslint": "8.59.1",
26
+ "typescript-eslint": "8.59.2",
27
27
  "vitest": "4.1.5"
28
28
  }
29
29
  }
@@ -1,47 +1,27 @@
1
1
  import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
2
- import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest";
2
+ import { tailordbMock } from "@tailor-platform/sdk/vitest";
3
+ import { beforeEach, describe, expect, test } from "vitest";
3
4
  import resolver from "./getProduct";
4
5
 
5
6
  describe("getProduct resolver", () => {
6
- const mockQueryObject = vi.fn();
7
- beforeAll(() => {
8
- vi.stubGlobal("tailordb", {
9
- Client: vi.fn(
10
- class {
11
- connect = vi.fn();
12
- end = vi.fn();
13
- queryObject = mockQueryObject;
14
- },
15
- ),
16
- });
17
- });
18
- afterAll(() => {
19
- vi.unstubAllGlobals();
20
- });
21
- afterEach(() => {
22
- mockQueryObject.mockReset();
7
+ beforeEach(() => {
8
+ tailordbMock.reset();
23
9
  });
24
10
 
25
11
  test("returns product with category", async () => {
26
12
  // Select product
27
- mockQueryObject.mockResolvedValueOnce({
28
- rows: [
29
- {
30
- id: "product-1",
31
- name: "Widget",
32
- price: 9.99,
33
- status: "ACTIVE",
34
- categoryId: "cat-1",
35
- description: null,
36
- createdAt: new Date(),
37
- updatedAt: null,
38
- },
39
- ],
13
+ tailordbMock.enqueueResult({
14
+ id: "product-1",
15
+ name: "Widget",
16
+ price: 9.99,
17
+ status: "ACTIVE",
18
+ categoryId: "cat-1",
19
+ description: null,
20
+ createdAt: new Date(),
21
+ updatedAt: null,
40
22
  });
41
23
  // Select category
42
- mockQueryObject.mockResolvedValueOnce({
43
- rows: [{ name: "Gadgets" }],
44
- });
24
+ tailordbMock.enqueueResult({ name: "Gadgets" });
45
25
 
46
26
  const result = await resolver.body({
47
27
  input: { productId: "product-1" },
@@ -55,24 +35,20 @@ describe("getProduct resolver", () => {
55
35
  status: "ACTIVE",
56
36
  categoryName: "Gadgets",
57
37
  });
58
- expect(mockQueryObject).toHaveBeenCalledTimes(2);
38
+ expect(tailordbMock.executedQueries).toHaveLength(2);
59
39
  });
60
40
 
61
41
  test("returns product without category", async () => {
62
42
  // Select product (no categoryId)
63
- mockQueryObject.mockResolvedValueOnce({
64
- rows: [
65
- {
66
- id: "product-2",
67
- name: "Standalone Item",
68
- price: 19.99,
69
- status: "DRAFT",
70
- categoryId: null,
71
- description: null,
72
- createdAt: new Date(),
73
- updatedAt: null,
74
- },
75
- ],
43
+ tailordbMock.enqueueResult({
44
+ id: "product-2",
45
+ name: "Standalone Item",
46
+ price: 19.99,
47
+ status: "DRAFT",
48
+ categoryId: null,
49
+ description: null,
50
+ createdAt: new Date(),
51
+ updatedAt: null,
76
52
  });
77
53
 
78
54
  const result = await resolver.body({
@@ -87,6 +63,6 @@ describe("getProduct resolver", () => {
87
63
  status: "DRAFT",
88
64
  categoryName: null,
89
65
  });
90
- expect(mockQueryObject).toHaveBeenCalledTimes(1);
66
+ expect(tailordbMock.executedQueries).toHaveLength(1);
91
67
  });
92
68
  });
@@ -1,12 +1,16 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { tailorRuntime } from "@tailor-platform/sdk/vitest";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [tailorRuntime()],
4
6
  test: {
5
7
  watch: false,
6
8
  projects: [
7
9
  {
10
+ extends: true,
8
11
  test: {
9
12
  name: { label: "unit", color: "blue" },
13
+ environment: "tailor-runtime",
10
14
  include: ["src/**/*.test.ts"],
11
15
  },
12
16
  },
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "@eslint/js": "10.0.1",
16
- "@tailor-platform/sdk": "1.46.0",
17
- "@types/node": "24.12.2",
16
+ "@tailor-platform/sdk": "1.47.1",
17
+ "@types/node": "24.12.3",
18
18
  "eslint": "10.3.0",
19
19
  "eslint-plugin-oxlint": "1.61.0",
20
20
  "oxfmt": "0.46.0",
21
21
  "oxlint": "1.61.0",
22
22
  "oxlint-tsgolint": "0.22.1",
23
23
  "typescript": "5.9.3",
24
- "typescript-eslint": "8.59.1"
24
+ "typescript-eslint": "8.59.2"
25
25
  }
26
26
  }
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "@eslint/js": "10.0.1",
16
- "@tailor-platform/sdk": "1.46.0",
17
- "@types/node": "24.12.2",
16
+ "@tailor-platform/sdk": "1.47.1",
17
+ "@types/node": "24.12.3",
18
18
  "eslint": "10.3.0",
19
19
  "eslint-plugin-oxlint": "1.61.0",
20
20
  "oxfmt": "0.46.0",
21
21
  "oxlint": "1.61.0",
22
22
  "oxlint-tsgolint": "0.22.1",
23
23
  "typescript": "5.9.3",
24
- "typescript-eslint": "8.59.1"
24
+ "typescript-eslint": "8.59.2"
25
25
  }
26
26
  }
@@ -14,14 +14,14 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@eslint/js": "10.0.1",
17
- "@tailor-platform/sdk": "1.46.0",
18
- "@types/node": "24.12.2",
17
+ "@tailor-platform/sdk": "1.47.1",
18
+ "@types/node": "24.12.3",
19
19
  "eslint": "10.3.0",
20
20
  "eslint-plugin-oxlint": "1.61.0",
21
21
  "oxfmt": "0.46.0",
22
22
  "oxlint": "1.61.0",
23
23
  "oxlint-tsgolint": "0.22.1",
24
24
  "typescript": "5.9.3",
25
- "typescript-eslint": "8.59.1"
25
+ "typescript-eslint": "8.59.2"
26
26
  }
27
27
  }
@@ -13,7 +13,7 @@ Demonstrates all resolver patterns with comprehensive testing approaches.
13
13
  ## Testing Approaches
14
14
 
15
15
  1. **Direct `body()` call** - Simple resolvers with `unauthenticatedTailorUser`
16
- 2. **Mock `tailordb.Client`** - Database resolvers via `vi.stubGlobal`
16
+ 2. **`tailor-runtime` environment + `tailordbMock`** - Database resolvers via `tailordbMock` from `@tailor-platform/sdk/vitest` (no `vi.stubGlobal` needed)
17
17
  3. **Dependency injection** - Extract `DbOperations` interface for testability
18
18
 
19
19
  ## Getting Started
@@ -7,8 +7,6 @@
7
7
  "deploy": "tailor-sdk deploy",
8
8
  "test": "vitest --project unit",
9
9
  "test:unit": "vitest --project unit",
10
- "test:bundled:prepare": "TAILOR_PLATFORM_SDK_BUILD_ONLY=true tailor-sdk deploy -c tailor.config.ts",
11
- "test:bundled": "pnpm test:bundled:prepare && vitest --project bundled",
12
10
  "format": "oxfmt --write .",
13
11
  "format:check": "oxfmt --check .",
14
12
  "lint": "oxlint --type-aware . && eslint --cache .",
@@ -17,15 +15,15 @@
17
15
  },
18
16
  "devDependencies": {
19
17
  "@eslint/js": "10.0.1",
20
- "@tailor-platform/sdk": "1.46.0",
21
- "@types/node": "24.12.2",
18
+ "@tailor-platform/sdk": "1.47.1",
19
+ "@types/node": "24.12.3",
22
20
  "eslint": "10.3.0",
23
21
  "eslint-plugin-oxlint": "1.61.0",
24
22
  "oxfmt": "0.46.0",
25
23
  "oxlint": "1.61.0",
26
24
  "oxlint-tsgolint": "0.22.1",
27
25
  "typescript": "5.9.3",
28
- "typescript-eslint": "8.59.1",
26
+ "typescript-eslint": "8.59.2",
29
27
  "vitest": "4.1.5"
30
28
  }
31
29
  }
@@ -0,0 +1,43 @@
1
+ import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
2
+ import { tailordbMock } from "@tailor-platform/sdk/vitest";
3
+ import { beforeEach, describe, expect, test } from "vitest";
4
+ import resolver from "./incrementUserAge";
5
+
6
+ describe("incrementUserAge resolver", () => {
7
+ beforeEach(() => {
8
+ tailordbMock.reset();
9
+ });
10
+
11
+ test("increments user age", async () => {
12
+ tailordbMock.enqueueResults(
13
+ [], // BEGIN
14
+ [{ age: 30 }], // SELECT
15
+ [], // UPDATE
16
+ [], // COMMIT
17
+ );
18
+
19
+ const result = await resolver.body({
20
+ input: { email: "test@example.com" },
21
+ user: unauthenticatedTailorUser,
22
+ env: { appName: "Resolver Template", version: 1 },
23
+ });
24
+ expect(result).toEqual({ oldAge: 30, newAge: 31 });
25
+ expect(tailordbMock.executedQueries).toHaveLength(4);
26
+ });
27
+
28
+ test("throws when user not found", async () => {
29
+ tailordbMock.enqueueResults(
30
+ [], // BEGIN
31
+ [], // SELECT (empty)
32
+ [], // ROLLBACK
33
+ );
34
+
35
+ const result = resolver.body({
36
+ input: { email: "test@example.com" },
37
+ user: unauthenticatedTailorUser,
38
+ env: { appName: "Resolver Template", version: 1 },
39
+ });
40
+ await expect(result).rejects.toThrowError();
41
+ expect(tailordbMock.executedQueries).toHaveLength(3);
42
+ });
43
+ });
@@ -1,21 +1,19 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { tailorRuntime } from "@tailor-platform/sdk/vitest";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [tailorRuntime()],
4
6
  test: {
5
7
  watch: false,
6
8
  projects: [
7
9
  {
10
+ extends: true,
8
11
  test: {
9
12
  name: { label: "unit", color: "blue" },
13
+ environment: "tailor-runtime",
10
14
  include: ["src/**/*.test.ts"],
11
15
  },
12
16
  },
13
- {
14
- test: {
15
- name: { label: "bundled", color: "yellow" },
16
- include: ["tests/**/*.test.ts"],
17
- },
18
- },
19
17
  ],
20
18
  },
21
19
  });
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "@eslint/js": "10.0.1",
16
- "@tailor-platform/sdk": "1.46.0",
17
- "@types/node": "24.12.2",
16
+ "@tailor-platform/sdk": "1.47.1",
17
+ "@types/node": "24.12.3",
18
18
  "eslint": "10.3.0",
19
19
  "eslint-plugin-oxlint": "1.61.0",
20
20
  "oxfmt": "0.46.0",
21
21
  "oxlint": "1.61.0",
22
22
  "oxlint-tsgolint": "0.22.1",
23
23
  "typescript": "5.9.3",
24
- "typescript-eslint": "8.59.1"
24
+ "typescript-eslint": "8.59.2"
25
25
  }
26
26
  }
@@ -15,15 +15,15 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@eslint/js": "10.0.1",
18
- "@tailor-platform/sdk": "1.46.0",
19
- "@types/node": "24.12.2",
18
+ "@tailor-platform/sdk": "1.47.1",
19
+ "@types/node": "24.12.3",
20
20
  "eslint": "10.3.0",
21
21
  "eslint-plugin-oxlint": "1.61.0",
22
22
  "oxfmt": "0.46.0",
23
23
  "oxlint": "1.61.0",
24
24
  "oxlint-tsgolint": "0.22.1",
25
25
  "typescript": "5.9.3",
26
- "typescript-eslint": "8.59.1",
26
+ "typescript-eslint": "8.59.2",
27
27
  "vitest": "4.1.5"
28
28
  }
29
29
  }
@@ -1,12 +1,16 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { tailorRuntime } from "@tailor-platform/sdk/vitest";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [tailorRuntime()],
4
6
  test: {
5
7
  watch: false,
6
8
  projects: [
7
9
  {
10
+ extends: true,
8
11
  test: {
9
12
  name: { label: "unit", color: "blue" },
13
+ environment: "tailor-runtime",
10
14
  include: ["src/**/*.test.ts"],
11
15
  },
12
16
  },
@@ -7,8 +7,6 @@
7
7
  "deploy": "tailor-sdk deploy",
8
8
  "test": "vitest --project unit",
9
9
  "test:unit": "vitest --project unit",
10
- "test:bundled:prepare": "TAILOR_PLATFORM_SDK_BUILD_ONLY=true tailor-sdk deploy -c tailor.config.ts",
11
- "test:bundled": "pnpm test:bundled:prepare && vitest --project bundled",
12
10
  "test:e2e": "vitest --project e2e",
13
11
  "format": "oxfmt --write .",
14
12
  "format:check": "oxfmt --check .",
@@ -18,8 +16,8 @@
18
16
  },
19
17
  "devDependencies": {
20
18
  "@eslint/js": "10.0.1",
21
- "@tailor-platform/sdk": "1.46.0",
22
- "@types/node": "24.12.2",
19
+ "@tailor-platform/sdk": "1.47.1",
20
+ "@types/node": "24.12.3",
23
21
  "eslint": "10.3.0",
24
22
  "eslint-plugin-oxlint": "1.61.0",
25
23
  "graphql": "16.13.2",
@@ -28,7 +26,7 @@
28
26
  "oxlint": "1.61.0",
29
27
  "oxlint-tsgolint": "0.22.1",
30
28
  "typescript": "5.9.3",
31
- "typescript-eslint": "8.59.1",
29
+ "typescript-eslint": "8.59.2",
32
30
  "vitest": "4.1.5"
33
31
  }
34
32
  }
@@ -1,20 +1,20 @@
1
- import { afterEach, describe, expect, test } from "vitest";
2
- import { setupWaitPointMock, unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import { workflowMock } from "@tailor-platform/sdk/vitest";
3
+ import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
3
4
  import resolver from "./resolveApproval";
4
5
 
5
- const TailorGlobal = globalThis as { tailor?: { workflow?: Record<string, unknown> } };
6
-
7
6
  describe("resolveApproval resolver", () => {
8
- afterEach(() => {
9
- delete TailorGlobal.tailor;
7
+ beforeEach(() => {
8
+ workflowMock.reset();
10
9
  });
11
10
 
12
11
  test("resolves approval with approved=true", async () => {
13
- const { resolveCalls } = setupWaitPointMock({
14
- onResolve: (_execId, _key, callback) => {
15
- const result = callback({ message: "Please approve order order-1", orderId: "order-1" });
16
- expect(result).toEqual({ approved: true });
17
- },
12
+ workflowMock.setResolveHandler((_executionId, _key, callback) => {
13
+ const callbackResult = callback({
14
+ message: "Please approve order order-1",
15
+ orderId: "order-1",
16
+ });
17
+ expect(callbackResult).toEqual({ approved: true });
18
18
  });
19
19
 
20
20
  const result = await resolver.body({
@@ -24,16 +24,13 @@ describe("resolveApproval resolver", () => {
24
24
  });
25
25
 
26
26
  expect(result).toEqual({ resolved: true });
27
- expect(resolveCalls).toHaveLength(1);
28
- expect(resolveCalls[0]).toEqual({ executionId: "exec-1", key: "approval" });
27
+ expect(workflowMock.resolveCalls).toEqual([{ executionId: "exec-1", key: "approval" }]);
29
28
  });
30
29
 
31
30
  test("resolves approval with approved=false", async () => {
32
- setupWaitPointMock({
33
- onResolve: (_execId, _key, callback) => {
34
- const result = callback({ message: "Please approve", orderId: "order-2" });
35
- expect(result).toEqual({ approved: false });
36
- },
31
+ workflowMock.setResolveHandler((_executionId, _key, callback) => {
32
+ const callbackResult = callback({ message: "Please approve", orderId: "order-2" });
33
+ expect(callbackResult).toEqual({ approved: false });
37
34
  });
38
35
 
39
36
  const result = await resolver.body({
@@ -1,34 +1,28 @@
1
- import { afterEach, describe, expect, test, vi } from "vitest";
2
- import { setupWaitPointMock } from "@tailor-platform/sdk/test";
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import { workflowMock } from "@tailor-platform/sdk/vitest";
3
3
  import workflow, { processWithApproval } from "./approval";
4
4
 
5
- const TailorGlobal = globalThis as { tailor?: { workflow?: Record<string, unknown> } };
6
-
7
5
  describe("approval workflow", () => {
8
- afterEach(() => {
9
- delete TailorGlobal.tailor;
10
- vi.restoreAllMocks();
6
+ beforeEach(() => {
7
+ workflowMock.reset();
11
8
  });
12
9
 
13
10
  test("approved flow returns approved status", async () => {
14
- const { waitCalls } = setupWaitPointMock({
15
- onWait: (_key, _payload) => ({ approved: true }),
16
- });
11
+ workflowMock.setWaitHandler((_key, _payload) => ({ approved: true }));
17
12
 
18
13
  const result = await processWithApproval.body({ orderId: "order-1" }, { env: {} });
19
14
 
20
15
  expect(result).toEqual({ orderId: "order-1", status: "approved" });
21
- expect(waitCalls).toHaveLength(1);
22
- expect(waitCalls[0]).toEqual({
23
- key: "approval",
24
- payload: { message: "Please approve order order-1", orderId: "order-1" },
25
- });
16
+ expect(workflowMock.waitCalls).toEqual([
17
+ {
18
+ key: "approval",
19
+ payload: { message: "Please approve order order-1", orderId: "order-1" },
20
+ },
21
+ ]);
26
22
  });
27
23
 
28
24
  test("rejected flow returns rejected status", async () => {
29
- setupWaitPointMock({
30
- onWait: () => ({ approved: false }),
31
- });
25
+ workflowMock.setWaitHandler({ approved: false });
32
26
 
33
27
  const result = await processWithApproval.body({ orderId: "order-2" }, { env: {} });
34
28
 
@@ -1,22 +1,21 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { tailorRuntime } from "@tailor-platform/sdk/vitest";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [tailorRuntime()],
4
6
  test: {
5
7
  watch: false,
6
8
  projects: [
7
9
  {
10
+ extends: true,
8
11
  test: {
9
12
  name: { label: "unit", color: "blue" },
13
+ environment: "tailor-runtime",
10
14
  include: ["src/**/*.test.ts"],
11
15
  },
12
16
  },
13
17
  {
14
- test: {
15
- name: { label: "bundled", color: "yellow" },
16
- include: ["tests/**/*.test.ts"],
17
- },
18
- },
19
- {
18
+ extends: true,
20
19
  test: {
21
20
  name: { label: "e2e", color: "green" },
22
21
  include: ["e2e/**/*.test.ts"],
@@ -1,64 +0,0 @@
1
- import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
2
- import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest";
3
- import resolver from "./queryUser";
4
-
5
- describe("incrementUserAge resolver", () => {
6
- const mockQueryObject = vi.fn();
7
- beforeAll(() => {
8
- vi.stubGlobal("tailordb", {
9
- Client: vi.fn(
10
- class {
11
- connect = vi.fn();
12
- end = vi.fn();
13
- queryObject = mockQueryObject;
14
- },
15
- ),
16
- });
17
- });
18
- afterAll(() => {
19
- vi.unstubAllGlobals();
20
- });
21
- afterEach(() => {
22
- mockQueryObject.mockReset();
23
- });
24
-
25
- test("increments user age", async () => {
26
- // 1: Begin transaction
27
- mockQueryObject.mockResolvedValueOnce({});
28
- // 2: Select current age
29
- mockQueryObject.mockResolvedValueOnce({
30
- rows: [{ age: 30 }],
31
- });
32
- // 3: Update age
33
- mockQueryObject.mockResolvedValueOnce({});
34
- // 4: Commit transaction
35
- mockQueryObject.mockResolvedValueOnce({});
36
-
37
- const result = await resolver.body({
38
- input: { email: "test@example.com" },
39
- user: unauthenticatedTailorUser,
40
- env: { appName: "Resolver Template", version: 1 },
41
- });
42
- expect(result).toEqual({ oldAge: 30, newAge: 31 });
43
- expect(mockQueryObject).toHaveBeenCalledTimes(4);
44
- });
45
-
46
- test("throws when user not found", async () => {
47
- // 1: Begin transaction
48
- mockQueryObject.mockResolvedValueOnce({});
49
- // 2: Select current age (no rows returned)
50
- mockQueryObject.mockResolvedValueOnce({
51
- rows: [],
52
- });
53
- // 3: Rollback transaction
54
- mockQueryObject.mockResolvedValueOnce({});
55
-
56
- const result = resolver.body({
57
- input: { email: "test@example.com" },
58
- user: unauthenticatedTailorUser,
59
- env: { appName: "Resolver Template", version: 1 },
60
- });
61
- await expect(result).rejects.toThrowError();
62
- expect(mockQueryObject).toHaveBeenCalledTimes(3);
63
- });
64
- });
@@ -1,98 +0,0 @@
1
- import path from "node:path";
2
- import { createImportMain, setupInvokerMock, setupTailordbMock } from "@tailor-platform/sdk/test";
3
- import { beforeAll, beforeEach, describe, expect, test } from "vitest";
4
-
5
- const outputDir = path.join(__dirname, "../.tailor-sdk");
6
-
7
- describe("bundled resolver execution", () => {
8
- let executedQueries: { query: string; params: unknown[] }[];
9
-
10
- const importMain = createImportMain(outputDir);
11
-
12
- beforeAll(() => {
13
- ({ executedQueries } = setupTailordbMock());
14
- setupInvokerMock(null);
15
- });
16
-
17
- beforeEach(() => {
18
- executedQueries.length = 0;
19
- });
20
-
21
- describe("add resolver", () => {
22
- test("returns sum of inputs", async () => {
23
- const main = await importMain("resolvers/add.js");
24
- const result = await main({ input: { left: 3, right: 4 } });
25
- expect(result).toBe(7);
26
- });
27
- });
28
-
29
- describe("incrementUserAge resolver", () => {
30
- test("increments user age with DB mock", async () => {
31
- setupTailordbMock((query) => {
32
- if (query.includes("SELECT") || query.includes("select")) {
33
- return [{ age: 30 }];
34
- }
35
- return [];
36
- });
37
-
38
- const main = await importMain("resolvers/incrementUserAge.js");
39
- const result = await main({ input: { email: "test@example.com" } });
40
- expect(result).toEqual({ oldAge: 30, newAge: 31 });
41
- });
42
- });
43
-
44
- describe("decrementUserAge resolver", () => {
45
- test("decrements user age with DB mock", async () => {
46
- setupTailordbMock((query) => {
47
- if (query.includes("SELECT") || query.includes("select")) {
48
- return [
49
- {
50
- id: "user-1",
51
- email: "test@example.com",
52
- name: "Test",
53
- age: 30,
54
- createdAt: null,
55
- updatedAt: null,
56
- },
57
- ];
58
- }
59
- return [];
60
- });
61
-
62
- const main = await importMain("resolvers/decrementUserAge.js");
63
- const result = await main({ input: { email: "test@example.com" } });
64
- expect(result).toEqual({ oldAge: 30, newAge: 29 });
65
- });
66
- });
67
-
68
- describe("showUserInfo resolver", () => {
69
- test("returns user info from context", async () => {
70
- const main = await importMain("resolvers/showUserInfo.js");
71
- const result = await main({
72
- user: {
73
- id: "test-id",
74
- type: "machine_user",
75
- workspaceId: "ws-id",
76
- attributes: { role: "admin" },
77
- attributeList: [],
78
- },
79
- });
80
- expect(result).toEqual({
81
- userId: "test-id",
82
- userType: "machine_user",
83
- workspaceId: "ws-id",
84
- });
85
- });
86
- });
87
-
88
- describe("showEnv resolver", () => {
89
- test("returns env values embedded from config", async () => {
90
- const main = await importMain("resolvers/showEnv.js");
91
- const result = await main({ env: { appName: "Resolver Template", version: 1 } });
92
- expect(result).toEqual({
93
- appName: "Resolver Template",
94
- version: 1,
95
- });
96
- });
97
- });
98
- });
@@ -1,174 +0,0 @@
1
- import path from "node:path";
2
- import {
3
- createImportMain,
4
- setupInvokerMock,
5
- setupTailordbMock,
6
- setupWaitPointMock,
7
- setupWorkflowMock,
8
- } from "@tailor-platform/sdk/test";
9
- import { beforeAll, beforeEach, describe, expect, test } from "vitest";
10
-
11
- const outputDir = path.join(__dirname, "../.tailor-sdk");
12
-
13
- describe("bundled workflow execution", () => {
14
- let executedQueries: { query: string; params: unknown[] }[];
15
-
16
- const importMain = createImportMain(outputDir);
17
-
18
- beforeAll(() => {
19
- ({ executedQueries } = setupTailordbMock());
20
- setupInvokerMock(null);
21
- });
22
-
23
- beforeEach(() => {
24
- executedQueries.length = 0;
25
- });
26
-
27
- describe("sync-profile job", () => {
28
- test("creates new user when not found", async () => {
29
- let selectCalled = false;
30
- setupTailordbMock((query) => {
31
- if (query.includes("SELECT") || query.includes("select")) {
32
- if (!selectCalled) {
33
- selectCalled = true;
34
- return [];
35
- }
36
- }
37
- if (query.includes("INSERT") || query.includes("insert")) {
38
- return [
39
- {
40
- id: "new-id",
41
- name: "Alice",
42
- email: "alice@example.com",
43
- age: 25,
44
- createdAt: "2024-01-01",
45
- updatedAt: null,
46
- },
47
- ];
48
- }
49
- return [];
50
- });
51
-
52
- const main = await importMain("workflow-jobs/sync-profile.js");
53
- const result = await main({ name: "Alice", email: "alice@example.com", age: 25 });
54
- expect(result).toEqual({
55
- created: true,
56
- profile: { name: "Alice", email: "alice@example.com", age: 25 },
57
- });
58
- });
59
-
60
- test("updates existing user when found", async () => {
61
- setupTailordbMock((query) => {
62
- if (query.includes("SELECT") || query.includes("select")) {
63
- return [
64
- {
65
- id: "existing-id",
66
- name: "Old Name",
67
- email: "alice@example.com",
68
- age: 20,
69
- createdAt: "2024-01-01",
70
- updatedAt: null,
71
- },
72
- ];
73
- }
74
- return [];
75
- });
76
-
77
- const main = await importMain("workflow-jobs/sync-profile.js");
78
- const result = await main({ name: "Alice Updated", email: "alice@example.com", age: 26 });
79
- expect(result).toEqual({
80
- created: false,
81
- profile: { name: "Alice Updated", email: "alice@example.com", age: 26 },
82
- });
83
- });
84
- });
85
-
86
- describe("approval workflow job", () => {
87
- test("process-with-approval returns approved when resolved with true", async () => {
88
- setupWaitPointMock({
89
- onWait: (_key, _payload) => ({ approved: true }),
90
- });
91
-
92
- const main = await importMain("workflow-jobs/process-with-approval.js");
93
- const result = await main({ orderId: "order-1" });
94
- expect(result).toEqual({ orderId: "order-1", status: "approved" });
95
- });
96
-
97
- test("process-with-approval returns rejected when resolved with false", async () => {
98
- setupWaitPointMock({
99
- onWait: (_key, _payload) => ({ approved: false }),
100
- });
101
-
102
- const main = await importMain("workflow-jobs/process-with-approval.js");
103
- const result = await main({ orderId: "order-2" });
104
- expect(result).toEqual({ orderId: "order-2", status: "rejected" });
105
- });
106
- });
107
-
108
- describe("order-fulfillment jobs", () => {
109
- test("validate-order validates positive amount", async () => {
110
- const main = await importMain("workflow-jobs/validate-order.js");
111
- const result = await main({ orderId: "order-1", amount: 100 });
112
- expect(result).toEqual({ valid: true, orderId: "order-1" });
113
- });
114
-
115
- test("validate-order throws for non-positive amount", async () => {
116
- const main = await importMain("workflow-jobs/validate-order.js");
117
- await expect(main({ orderId: "order-1", amount: 0 })).rejects.toThrow(
118
- "Order amount must be positive",
119
- );
120
- });
121
-
122
- test("process-payment returns transaction", async () => {
123
- const main = await importMain("workflow-jobs/process-payment.js");
124
- const result = await main({ orderId: "order-1", amount: 100 });
125
- expect(result).toEqual({
126
- transactionId: "txn-order-1",
127
- amount: 100,
128
- status: "completed",
129
- });
130
- });
131
-
132
- test("send-confirmation returns confirmation", async () => {
133
- const main = await importMain("workflow-jobs/send-confirmation.js");
134
- const result = await main({ orderId: "order-1", transactionId: "txn-order-1" });
135
- expect(result).toEqual({
136
- orderId: "order-1",
137
- transactionId: "txn-order-1",
138
- confirmed: true,
139
- });
140
- });
141
-
142
- test("fulfill-order orchestrates all jobs", async () => {
143
- setupWorkflowMock((jobName, args) => {
144
- switch (jobName) {
145
- case "validate-order":
146
- return { valid: true, orderId: (args as { orderId: string }).orderId };
147
- case "process-payment":
148
- return {
149
- transactionId: `txn-${(args as { orderId: string }).orderId}`,
150
- amount: (args as { amount: number }).amount,
151
- status: "completed",
152
- };
153
- case "send-confirmation":
154
- return {
155
- orderId: (args as { orderId: string }).orderId,
156
- transactionId: (args as { transactionId: string }).transactionId,
157
- confirmed: true,
158
- };
159
- default:
160
- throw new Error(`Unknown job: ${jobName}`);
161
- }
162
- });
163
-
164
- const main = await importMain("workflow-jobs/fulfill-order.js");
165
- const result = await main({ orderId: "order-1", amount: 100 });
166
- expect(result).toEqual({
167
- orderId: "order-1",
168
- transactionId: "txn-order-1",
169
- confirmed: true,
170
- paymentStatus: "completed",
171
- });
172
- });
173
- });
174
- });