@tailor-platform/create-sdk 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/index.js +7 -2
- package/package.json +4 -3
- package/templates/executor/README.md +32 -0
- package/templates/{testing → executor}/eslint.config.js +0 -5
- package/templates/{testing → executor}/package.json +2 -4
- package/templates/executor/src/db/auditLog.ts +24 -0
- package/templates/executor/src/db/notification.ts +27 -0
- package/templates/executor/src/db/user.ts +22 -0
- package/templates/executor/src/executor/dailyCleanup.ts +16 -0
- package/templates/executor/src/executor/externalWebhook.ts +18 -0
- package/templates/executor/src/executor/onDataProcessed.ts +26 -0
- package/templates/executor/src/executor/onIdpUserCreated.ts +19 -0
- package/templates/executor/src/executor/onIdpUserDeleted.ts +19 -0
- package/templates/executor/src/executor/onIdpUserUpdated.ts +19 -0
- package/templates/executor/src/executor/onTokenIssued.ts +19 -0
- package/templates/executor/src/executor/onTokenRefreshed.ts +19 -0
- package/templates/executor/src/executor/onTokenRevoked.ts +19 -0
- package/templates/executor/src/executor/onUserCreated.ts +23 -0
- package/templates/executor/src/executor/onUserDeleted.ts +22 -0
- package/templates/executor/src/executor/onUserUpdated.ts +22 -0
- package/templates/executor/src/executor/shared.test.ts +36 -0
- package/templates/executor/src/executor/shared.ts +13 -0
- package/templates/executor/src/generated/db.ts +59 -0
- package/templates/executor/src/resolver/processData.ts +22 -0
- package/templates/executor/src/workflow/cleanup.ts +13 -0
- package/templates/executor/tailor.config.ts +40 -0
- package/templates/executor/vitest.config.ts +15 -0
- package/templates/generators/.oxfmtrc.json +3 -0
- package/templates/generators/.oxlintrc.json +197 -0
- package/templates/generators/.prettierignore +1 -0
- package/templates/generators/.prettierrc +1 -0
- package/templates/generators/README.md +30 -0
- package/templates/generators/__dot__gitignore +3 -0
- package/templates/generators/eslint.config.js +24 -0
- package/templates/generators/package.json +30 -0
- package/templates/generators/src/db/category.ts +25 -0
- package/templates/generators/src/db/order.ts +38 -0
- package/templates/generators/src/db/product.ts +34 -0
- package/templates/generators/src/db/user.ts +26 -0
- package/templates/generators/src/generated/db.ts +68 -0
- package/templates/generators/src/generated/enums.ts +39 -0
- package/templates/generators/src/generated/files.ts +51 -0
- package/templates/generators/src/resolver/getProduct.test.ts +92 -0
- package/templates/generators/src/resolver/getProduct.ts +53 -0
- package/templates/generators/src/seed/data/Category.jsonl +0 -0
- package/templates/generators/src/seed/data/Category.schema.ts +23 -0
- package/templates/generators/src/seed/data/Order.jsonl +0 -0
- package/templates/generators/src/seed/data/Order.schema.ts +21 -0
- package/templates/generators/src/seed/data/Product.jsonl +0 -0
- package/templates/generators/src/seed/data/Product.schema.ts +23 -0
- package/templates/generators/src/seed/data/User.jsonl +0 -0
- package/templates/generators/src/seed/data/User.schema.ts +20 -0
- package/templates/generators/src/seed/exec.mjs +419 -0
- package/templates/generators/tailor.config.ts +36 -0
- package/templates/generators/tsconfig.json +16 -0
- package/templates/generators/vitest.config.ts +15 -0
- package/templates/hello-world/package.json +1 -1
- package/templates/inventory-management/package.json +1 -1
- package/templates/inventory-management/user-defined.d.ts +15 -0
- package/templates/multi-application/package.json +1 -1
- package/templates/resolver/.oxfmtrc.json +3 -0
- package/templates/resolver/.oxlintrc.json +197 -0
- package/templates/resolver/.prettierrc +1 -0
- package/templates/resolver/README.md +31 -0
- package/templates/resolver/__dot__gitignore +3 -0
- package/templates/resolver/eslint.config.js +24 -0
- package/templates/resolver/package.json +32 -0
- package/templates/resolver/src/resolver/add.test.ts +23 -0
- package/templates/{testing/src/resolver/mockTailordb.test.ts → resolver/src/resolver/queryUser.test.ts} +5 -6
- package/templates/{testing/src/resolver/mockTailordb.ts → resolver/src/resolver/queryUser.ts} +0 -5
- package/templates/resolver/src/resolver/showEnv.test.ts +14 -0
- package/templates/resolver/src/resolver/showEnv.ts +19 -0
- package/templates/resolver/src/resolver/showUserInfo.test.ts +37 -0
- package/templates/resolver/src/resolver/showUserInfo.ts +21 -0
- package/templates/{testing/src/resolver/wrapTailordb.test.ts → resolver/src/resolver/updateUser.test.ts} +3 -5
- package/templates/{testing/src/resolver/wrapTailordb.ts → resolver/src/resolver/updateUser.ts} +0 -5
- package/templates/resolver/tailor.config.ts +26 -0
- package/templates/resolver/tests/bundled.test.ts +97 -0
- package/templates/resolver/tsconfig.json +16 -0
- package/templates/resolver/user-defined.d.ts +18 -0
- package/templates/resolver/vitest.config.ts +21 -0
- package/templates/static-web-site/.oxfmtrc.json +3 -0
- package/templates/static-web-site/.oxlintrc.json +197 -0
- package/templates/static-web-site/.prettierrc +1 -0
- package/templates/static-web-site/README.md +21 -0
- package/templates/static-web-site/__dot__gitignore +3 -0
- package/templates/static-web-site/eslint.config.js +24 -0
- package/templates/static-web-site/package.json +27 -0
- package/templates/static-web-site/public/callback.html +34 -0
- package/templates/static-web-site/public/index.html +55 -0
- package/templates/static-web-site/public/style.css +62 -0
- package/templates/static-web-site/src/db/user.ts +22 -0
- package/templates/static-web-site/tailor.config.ts +55 -0
- package/templates/static-web-site/tsconfig.json +16 -0
- package/templates/tailordb/.oxfmtrc.json +3 -0
- package/templates/tailordb/.oxlintrc.json +197 -0
- package/templates/tailordb/.prettierrc +1 -0
- package/templates/tailordb/README.md +29 -0
- package/templates/tailordb/__dot__gitignore +3 -0
- package/templates/tailordb/eslint.config.js +24 -0
- package/templates/tailordb/package.json +30 -0
- package/templates/tailordb/src/db/category.ts +15 -0
- package/templates/tailordb/src/db/comment.ts +26 -0
- package/templates/tailordb/src/db/permission.ts +43 -0
- package/templates/tailordb/src/db/task.test.ts +41 -0
- package/templates/tailordb/src/db/task.ts +58 -0
- package/templates/tailordb/src/db/user.ts +19 -0
- package/templates/tailordb/src/generated/db.ts +75 -0
- package/templates/tailordb/tailor.config.ts +26 -0
- package/templates/tailordb/tsconfig.json +16 -0
- package/templates/tailordb/user-defined.d.ts +15 -0
- package/templates/tailordb/vitest.config.ts +15 -0
- package/templates/workflow/.oxfmtrc.json +3 -0
- package/templates/workflow/.oxlintrc.json +197 -0
- package/templates/workflow/.prettierrc +1 -0
- package/templates/workflow/README.md +24 -0
- package/templates/workflow/__dot__gitignore +3 -0
- package/templates/{testing → workflow}/e2e/globalSetup.ts +5 -5
- package/templates/workflow/e2e/resolver.test.ts +90 -0
- package/templates/workflow/e2e/workflow.test.ts +31 -0
- package/templates/workflow/eslint.config.js +24 -0
- package/templates/workflow/package.json +35 -0
- package/templates/workflow/src/db/order.ts +22 -0
- package/templates/workflow/src/db/user.ts +22 -0
- package/templates/workflow/src/generated/db.ts +48 -0
- package/templates/workflow/src/resolver/incrementAge.ts +40 -0
- package/templates/workflow/src/workflow/order-fulfillment.test.ts +133 -0
- package/templates/workflow/src/workflow/order-fulfillment.ts +69 -0
- package/templates/{testing/src/workflow/wrapTailordb.test.ts → workflow/src/workflow/sync-profile.test.ts} +1 -1
- package/templates/{testing → workflow}/tailor.config.ts +1 -1
- package/templates/workflow/tests/bundled.test.ts +145 -0
- package/templates/workflow/tsconfig.json +16 -0
- package/templates/workflow/user-defined.d.ts +15 -0
- package/templates/{testing → workflow}/vitest.config.ts +6 -0
- package/templates/testing/README.md +0 -130
- package/templates/testing/e2e/resolver.test.ts +0 -57
- package/templates/testing/e2e/workflow.test.ts +0 -47
- package/templates/testing/src/resolver/simple.test.ts +0 -14
- package/templates/testing/src/workflow/simple.test.ts +0 -88
- package/templates/testing/src/workflow/simple.ts +0 -36
- package/templates/testing/src/workflow/trigger.test.ts +0 -104
- /package/templates/{testing → executor}/.oxfmtrc.json +0 -0
- /package/templates/{testing → executor}/.oxlintrc.json +0 -0
- /package/templates/{testing → executor}/.prettierrc +0 -0
- /package/templates/{testing → executor}/__dot__gitignore +0 -0
- /package/templates/{testing → executor}/tsconfig.json +0 -0
- /package/templates/{testing → resolver}/src/db/user.ts +0 -0
- /package/templates/{testing → resolver}/src/generated/db.ts +0 -0
- /package/templates/{testing/src/resolver/simple.ts → resolver/src/resolver/add.ts} +0 -0
- /package/templates/{testing/src/workflow/wrapTailordb.ts → workflow/src/workflow/sync-profile.ts} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { WORKFLOW_TEST_ENV_KEY } from "@tailor-platform/sdk/test";
|
|
2
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import workflow, {
|
|
4
|
+
fulfillOrder,
|
|
5
|
+
processPayment,
|
|
6
|
+
sendConfirmation,
|
|
7
|
+
validateOrder,
|
|
8
|
+
} from "./order-fulfillment";
|
|
9
|
+
|
|
10
|
+
describe("order fulfillment workflow", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.unstubAllEnvs();
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("individual job tests with .body()", () => {
|
|
17
|
+
test("validateOrder accepts valid order", () => {
|
|
18
|
+
const result = validateOrder.body({ orderId: "order-1", amount: 100 }, { env: {} });
|
|
19
|
+
expect(result).toEqual({ valid: true, orderId: "order-1" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("validateOrder rejects zero amount", () => {
|
|
23
|
+
expect(() => validateOrder.body({ orderId: "order-1", amount: 0 }, { env: {} })).toThrow(
|
|
24
|
+
"Order amount must be positive",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("processPayment returns transaction", () => {
|
|
29
|
+
const result = processPayment.body({ orderId: "order-1", amount: 100 }, { env: {} });
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
transactionId: "txn-order-1",
|
|
32
|
+
amount: 100,
|
|
33
|
+
status: "completed",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("sendConfirmation returns confirmation", () => {
|
|
38
|
+
const result = sendConfirmation.body(
|
|
39
|
+
{ orderId: "order-1", transactionId: "txn-1" },
|
|
40
|
+
{ env: {} },
|
|
41
|
+
);
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
orderId: "order-1",
|
|
44
|
+
transactionId: "txn-1",
|
|
45
|
+
confirmed: true,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("orchestration tests with mocked triggers", () => {
|
|
51
|
+
test("fulfillOrder chains all jobs", async () => {
|
|
52
|
+
vi.spyOn(validateOrder, "trigger").mockResolvedValue({
|
|
53
|
+
valid: true,
|
|
54
|
+
orderId: "order-1",
|
|
55
|
+
});
|
|
56
|
+
vi.spyOn(processPayment, "trigger").mockResolvedValue({
|
|
57
|
+
transactionId: "txn-order-1",
|
|
58
|
+
amount: 100,
|
|
59
|
+
status: "completed" as const,
|
|
60
|
+
});
|
|
61
|
+
vi.spyOn(sendConfirmation, "trigger").mockResolvedValue({
|
|
62
|
+
orderId: "order-1",
|
|
63
|
+
transactionId: "txn-order-1",
|
|
64
|
+
confirmed: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const result = await fulfillOrder.body({ orderId: "order-1", amount: 100 }, { env: {} });
|
|
68
|
+
|
|
69
|
+
expect(validateOrder.trigger).toHaveBeenCalledWith({
|
|
70
|
+
orderId: "order-1",
|
|
71
|
+
amount: 100,
|
|
72
|
+
});
|
|
73
|
+
expect(processPayment.trigger).toHaveBeenCalledWith({
|
|
74
|
+
orderId: "order-1",
|
|
75
|
+
amount: 100,
|
|
76
|
+
});
|
|
77
|
+
expect(sendConfirmation.trigger).toHaveBeenCalledWith({
|
|
78
|
+
orderId: "order-1",
|
|
79
|
+
transactionId: "txn-order-1",
|
|
80
|
+
});
|
|
81
|
+
expect(result).toEqual({
|
|
82
|
+
orderId: "order-1",
|
|
83
|
+
transactionId: "txn-order-1",
|
|
84
|
+
confirmed: true,
|
|
85
|
+
paymentStatus: "completed",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("workflow.mainJob.body() chains all jobs", async () => {
|
|
90
|
+
vi.spyOn(validateOrder, "trigger").mockResolvedValue({
|
|
91
|
+
valid: true,
|
|
92
|
+
orderId: "order-2",
|
|
93
|
+
});
|
|
94
|
+
vi.spyOn(processPayment, "trigger").mockResolvedValue({
|
|
95
|
+
transactionId: "txn-order-2",
|
|
96
|
+
amount: 200,
|
|
97
|
+
status: "completed" as const,
|
|
98
|
+
});
|
|
99
|
+
vi.spyOn(sendConfirmation, "trigger").mockResolvedValue({
|
|
100
|
+
orderId: "order-2",
|
|
101
|
+
transactionId: "txn-order-2",
|
|
102
|
+
confirmed: true,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = await workflow.mainJob.body({ orderId: "order-2", amount: 200 }, { env: {} });
|
|
106
|
+
|
|
107
|
+
expect(result).toEqual({
|
|
108
|
+
orderId: "order-2",
|
|
109
|
+
transactionId: "txn-order-2",
|
|
110
|
+
confirmed: true,
|
|
111
|
+
paymentStatus: "completed",
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("integration tests with .trigger()", () => {
|
|
117
|
+
test("workflow.mainJob.trigger() executes all jobs", async () => {
|
|
118
|
+
vi.stubEnv(WORKFLOW_TEST_ENV_KEY, JSON.stringify({}));
|
|
119
|
+
|
|
120
|
+
const result = await workflow.mainJob.trigger({
|
|
121
|
+
orderId: "order-3",
|
|
122
|
+
amount: 300,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result).toEqual({
|
|
126
|
+
orderId: "order-3",
|
|
127
|
+
transactionId: "txn-order-3",
|
|
128
|
+
confirmed: true,
|
|
129
|
+
paymentStatus: "completed",
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createWorkflow, createWorkflowJob } from "@tailor-platform/sdk";
|
|
2
|
+
|
|
3
|
+
export const validateOrder = createWorkflowJob({
|
|
4
|
+
name: "validate-order",
|
|
5
|
+
body: (input: { orderId: string; amount: number }) => {
|
|
6
|
+
if (input.amount <= 0) {
|
|
7
|
+
throw new Error("Order amount must be positive");
|
|
8
|
+
}
|
|
9
|
+
return { valid: true, orderId: input.orderId };
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const processPayment = createWorkflowJob({
|
|
14
|
+
name: "process-payment",
|
|
15
|
+
body: (input: { orderId: string; amount: number }) => {
|
|
16
|
+
return {
|
|
17
|
+
transactionId: `txn-${input.orderId}`,
|
|
18
|
+
amount: input.amount,
|
|
19
|
+
status: "completed" as const,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const sendConfirmation = createWorkflowJob({
|
|
25
|
+
name: "send-confirmation",
|
|
26
|
+
body: (input: { orderId: string; transactionId: string }) => {
|
|
27
|
+
return {
|
|
28
|
+
orderId: input.orderId,
|
|
29
|
+
transactionId: input.transactionId,
|
|
30
|
+
confirmed: true,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const fulfillOrder = createWorkflowJob({
|
|
36
|
+
name: "fulfill-order",
|
|
37
|
+
body: async (input: { orderId: string; amount: number }) => {
|
|
38
|
+
const validation = await validateOrder.trigger({
|
|
39
|
+
orderId: input.orderId,
|
|
40
|
+
amount: input.amount,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!validation.valid) {
|
|
44
|
+
throw new Error("Order validation failed");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const payment = await processPayment.trigger({
|
|
48
|
+
orderId: input.orderId,
|
|
49
|
+
amount: input.amount,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const confirmation = await sendConfirmation.trigger({
|
|
53
|
+
orderId: input.orderId,
|
|
54
|
+
transactionId: payment.transactionId,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
orderId: confirmation.orderId,
|
|
59
|
+
transactionId: confirmation.transactionId,
|
|
60
|
+
confirmed: confirmation.confirmed,
|
|
61
|
+
paymentStatus: payment.status,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default createWorkflow({
|
|
67
|
+
name: "order-fulfillment",
|
|
68
|
+
mainJob: fulfillOrder,
|
|
69
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { type DbOperations, syncUserProfile } from "./
|
|
2
|
+
import { type DbOperations, syncUserProfile } from "./sync-profile";
|
|
3
3
|
|
|
4
4
|
describe("syncUserProfile workflow", () => {
|
|
5
5
|
test("creates new user when not found", async () => {
|
|
@@ -2,7 +2,7 @@ import { defineAuth, defineConfig, definePlugins, t } from "@tailor-platform/sdk
|
|
|
2
2
|
import { kyselyTypePlugin } from "@tailor-platform/sdk/plugin/kysely-type";
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
name: "
|
|
5
|
+
name: "workflow",
|
|
6
6
|
auth: defineAuth("main-auth", {
|
|
7
7
|
machineUserAttributes: {
|
|
8
8
|
role: t.string(),
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createImportMain, setupTailordbMock, setupWorkflowMock } 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 workflow execution", () => {
|
|
8
|
+
let executedQueries: { query: string; params: unknown[] }[];
|
|
9
|
+
|
|
10
|
+
const importMain = createImportMain(outputDir);
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
({ executedQueries } = setupTailordbMock());
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
executedQueries.length = 0;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("sync-profile job", () => {
|
|
21
|
+
test("creates new user when not found", async () => {
|
|
22
|
+
let selectCalled = false;
|
|
23
|
+
setupTailordbMock((query) => {
|
|
24
|
+
if (query.includes("SELECT") || query.includes("select")) {
|
|
25
|
+
if (!selectCalled) {
|
|
26
|
+
selectCalled = true;
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (query.includes("INSERT") || query.includes("insert")) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
id: "new-id",
|
|
34
|
+
name: "Alice",
|
|
35
|
+
email: "alice@example.com",
|
|
36
|
+
age: 25,
|
|
37
|
+
createdAt: "2024-01-01",
|
|
38
|
+
updatedAt: null,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const main = await importMain("workflow-jobs/sync-profile.js");
|
|
46
|
+
const result = await main({ name: "Alice", email: "alice@example.com", age: 25 });
|
|
47
|
+
expect(result).toEqual({
|
|
48
|
+
created: true,
|
|
49
|
+
profile: { name: "Alice", email: "alice@example.com", age: 25 },
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("updates existing user when found", async () => {
|
|
54
|
+
setupTailordbMock((query) => {
|
|
55
|
+
if (query.includes("SELECT") || query.includes("select")) {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
id: "existing-id",
|
|
59
|
+
name: "Old Name",
|
|
60
|
+
email: "alice@example.com",
|
|
61
|
+
age: 20,
|
|
62
|
+
createdAt: "2024-01-01",
|
|
63
|
+
updatedAt: null,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const main = await importMain("workflow-jobs/sync-profile.js");
|
|
71
|
+
const result = await main({ name: "Alice Updated", email: "alice@example.com", age: 26 });
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
created: false,
|
|
74
|
+
profile: { name: "Alice Updated", email: "alice@example.com", age: 26 },
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("order-fulfillment jobs", () => {
|
|
80
|
+
test("validate-order validates positive amount", async () => {
|
|
81
|
+
const main = await importMain("workflow-jobs/validate-order.js");
|
|
82
|
+
const result = await main({ orderId: "order-1", amount: 100 });
|
|
83
|
+
expect(result).toEqual({ valid: true, orderId: "order-1" });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("validate-order throws for non-positive amount", async () => {
|
|
87
|
+
const main = await importMain("workflow-jobs/validate-order.js");
|
|
88
|
+
await expect(main({ orderId: "order-1", amount: 0 })).rejects.toThrow(
|
|
89
|
+
"Order amount must be positive",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("process-payment returns transaction", async () => {
|
|
94
|
+
const main = await importMain("workflow-jobs/process-payment.js");
|
|
95
|
+
const result = await main({ orderId: "order-1", amount: 100 });
|
|
96
|
+
expect(result).toEqual({
|
|
97
|
+
transactionId: "txn-order-1",
|
|
98
|
+
amount: 100,
|
|
99
|
+
status: "completed",
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("send-confirmation returns confirmation", async () => {
|
|
104
|
+
const main = await importMain("workflow-jobs/send-confirmation.js");
|
|
105
|
+
const result = await main({ orderId: "order-1", transactionId: "txn-order-1" });
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
orderId: "order-1",
|
|
108
|
+
transactionId: "txn-order-1",
|
|
109
|
+
confirmed: true,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("fulfill-order orchestrates all jobs", async () => {
|
|
114
|
+
setupWorkflowMock((jobName, args) => {
|
|
115
|
+
switch (jobName) {
|
|
116
|
+
case "validate-order":
|
|
117
|
+
return { valid: true, orderId: (args as { orderId: string }).orderId };
|
|
118
|
+
case "process-payment":
|
|
119
|
+
return {
|
|
120
|
+
transactionId: `txn-${(args as { orderId: string }).orderId}`,
|
|
121
|
+
amount: (args as { amount: number }).amount,
|
|
122
|
+
status: "completed",
|
|
123
|
+
};
|
|
124
|
+
case "send-confirmation":
|
|
125
|
+
return {
|
|
126
|
+
orderId: (args as { orderId: string }).orderId,
|
|
127
|
+
transactionId: (args as { transactionId: string }).transactionId,
|
|
128
|
+
confirmed: true,
|
|
129
|
+
};
|
|
130
|
+
default:
|
|
131
|
+
throw new Error(`Unknown job: ${jobName}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const main = await importMain("workflow-jobs/fulfill-order.js");
|
|
136
|
+
const result = await main({ orderId: "order-1", amount: 100 });
|
|
137
|
+
expect(result).toEqual({
|
|
138
|
+
orderId: "order-1",
|
|
139
|
+
transactionId: "txn-order-1",
|
|
140
|
+
confirmed: true,
|
|
141
|
+
paymentStatus: "completed",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"types": ["node", "@tailor-platform/function-types"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["**/*.ts"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// This file is auto-generated by @tailor-platform/sdk
|
|
2
|
+
// Do not edit this file manually
|
|
3
|
+
// Regenerated automatically when running 'tailor-sdk apply' or 'tailor-sdk generate'
|
|
4
|
+
|
|
5
|
+
declare module "@tailor-platform/sdk" {
|
|
6
|
+
interface AttributeMap {
|
|
7
|
+
role: string;
|
|
8
|
+
}
|
|
9
|
+
interface AttributeList {
|
|
10
|
+
__tuple?: [];
|
|
11
|
+
}
|
|
12
|
+
interface Env {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# Testing Guide
|
|
2
|
-
|
|
3
|
-
This guide covers testing patterns for Tailor Platform SDK applications using [Vitest](https://vitest.dev/).
|
|
4
|
-
|
|
5
|
-
This project was bootstrapped with [Create Tailor Platform SDK](https://www.npmjs.com/package/@tailor-platform/create-sdk).
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
### Unit Tests
|
|
10
|
-
|
|
11
|
-
Run unit tests locally without deployment:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm run test:unit
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### End-to-End (E2E) Tests
|
|
18
|
-
|
|
19
|
-
E2E tests require a deployed application.
|
|
20
|
-
|
|
21
|
-
#### Local Development
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# 1. Login
|
|
25
|
-
npx tailor-sdk login
|
|
26
|
-
|
|
27
|
-
# 2. Create workspace
|
|
28
|
-
npx tailor-sdk workspace create --name <workspace-name> --region <workspace-region>
|
|
29
|
-
|
|
30
|
-
# 3. Deploy application
|
|
31
|
-
export TAILOR_PLATFORM_WORKSPACE_ID=<your-workspace-id>
|
|
32
|
-
npm run deploy
|
|
33
|
-
|
|
34
|
-
# 4. Run E2E tests
|
|
35
|
-
npm run test:e2e
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
#### CI/CD (Automated Workspace Lifecycle)
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx tailor-sdk login
|
|
42
|
-
export CI=true
|
|
43
|
-
export TAILOR_PLATFORM_WORKSPACE_NAME=<workspace-name>
|
|
44
|
-
export TAILOR_PLATFORM_WORKSPACE_REGION=<workspace-region>
|
|
45
|
-
npm run test:e2e # Automatically creates, deploys, tests, and deletes workspace
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Testing Patterns
|
|
49
|
-
|
|
50
|
-
### Unit Tests
|
|
51
|
-
|
|
52
|
-
Unit tests verify resolver logic without requiring deployment.
|
|
53
|
-
|
|
54
|
-
#### Simple Resolver Testing
|
|
55
|
-
|
|
56
|
-
**Example:** [src/resolver/simple.test.ts](src/resolver/simple.test.ts)
|
|
57
|
-
|
|
58
|
-
Test resolvers by directly calling `resolver.body()` with mock inputs.
|
|
59
|
-
|
|
60
|
-
**Key points:**
|
|
61
|
-
|
|
62
|
-
- Use `unauthenticatedTailorUser` for testing logic that doesn't depend on user context
|
|
63
|
-
- **Best for:** Calculations, data transformations without database dependencies
|
|
64
|
-
|
|
65
|
-
#### Mock TailorDB Client
|
|
66
|
-
|
|
67
|
-
**Example:** [src/resolver/mockTailordb.test.ts](src/resolver/mockTailordb.test.ts)
|
|
68
|
-
|
|
69
|
-
Mock the global `tailordb.Client` using `vi.stubGlobal()` to simulate database operations and control responses for each query.
|
|
70
|
-
|
|
71
|
-
**Key points:**
|
|
72
|
-
|
|
73
|
-
- Control exact database responses (query results, errors)
|
|
74
|
-
- Verify database interaction flow (transactions, queries)
|
|
75
|
-
- Test transaction rollback scenarios
|
|
76
|
-
- **Best for:** Business logic with simple database operations
|
|
77
|
-
|
|
78
|
-
#### Dependency Injection Pattern
|
|
79
|
-
|
|
80
|
-
**Example:** [src/resolver/wrapTailordb.test.ts](src/resolver/wrapTailordb.test.ts)
|
|
81
|
-
|
|
82
|
-
Extract database operations into a `DbOperations` interface, allowing business logic to be tested independently from Kysely implementation.
|
|
83
|
-
|
|
84
|
-
**Key points:**
|
|
85
|
-
|
|
86
|
-
- Test business logic independently from Kysely implementation details
|
|
87
|
-
- Mock high-level operations instead of low-level SQL queries
|
|
88
|
-
- **Best for:** Complex business logic with multiple database operations
|
|
89
|
-
|
|
90
|
-
### End-to-End (E2E) Tests
|
|
91
|
-
|
|
92
|
-
E2E tests verify your application works correctly when deployed to Tailor Platform. They test the full stack including GraphQL API, database operations, and authentication.
|
|
93
|
-
|
|
94
|
-
**Examples:** [e2e/resolver.test.ts](e2e/resolver.test.ts), [e2e/globalSetup.ts](e2e/globalSetup.ts)
|
|
95
|
-
|
|
96
|
-
#### How It Works
|
|
97
|
-
|
|
98
|
-
**1. Global Setup** ([e2e/globalSetup.ts](e2e/globalSetup.ts))
|
|
99
|
-
|
|
100
|
-
Before running tests, `globalSetup` retrieves deployment information:
|
|
101
|
-
|
|
102
|
-
- Application URL via `show()`
|
|
103
|
-
- Machine user access token via `machineUserToken()`
|
|
104
|
-
- Provides credentials to tests via `inject("url")` and `inject("token")`
|
|
105
|
-
|
|
106
|
-
**2. Test Files** ([e2e/resolver.test.ts](e2e/resolver.test.ts))
|
|
107
|
-
|
|
108
|
-
Tests create a GraphQL client using injected credentials and send real queries/mutations to the deployed application.
|
|
109
|
-
|
|
110
|
-
**Key points:**
|
|
111
|
-
|
|
112
|
-
- Tests run against actual deployed application
|
|
113
|
-
- `inject("url")` and `inject("token")` provide deployment credentials automatically
|
|
114
|
-
- Machine user authentication enables API access without manual token management
|
|
115
|
-
- Verify database persistence and API contracts
|
|
116
|
-
- **Best for:** Integration testing, end-to-end API validation
|
|
117
|
-
|
|
118
|
-
## Available Scripts
|
|
119
|
-
|
|
120
|
-
| Script | Description |
|
|
121
|
-
| -------------- | -------------------------------------------- |
|
|
122
|
-
| `generate` | Generate TypeScript types from configuration |
|
|
123
|
-
| `deploy` | Deploy application to Tailor Platform |
|
|
124
|
-
| `test:unit` | Run unit tests locally |
|
|
125
|
-
| `test:e2e` | Run E2E tests against deployed application |
|
|
126
|
-
| `format` | Format code with Prettier |
|
|
127
|
-
| `format:check` | Check code formatting |
|
|
128
|
-
| `lint` | Lint code with ESLint |
|
|
129
|
-
| `lint:fix` | Fix linting issues automatically |
|
|
130
|
-
| `typecheck` | Run TypeScript type checking |
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { gql, GraphQLClient } from "graphql-request";
|
|
3
|
-
import { describe, expect, inject, test } from "vitest";
|
|
4
|
-
|
|
5
|
-
function createGraphQLClient() {
|
|
6
|
-
const endpoint = new URL("/query", inject("url")).href;
|
|
7
|
-
return new GraphQLClient(endpoint, {
|
|
8
|
-
headers: {
|
|
9
|
-
Authorization: `Bearer ${inject("token")}`,
|
|
10
|
-
},
|
|
11
|
-
// Prevent throwing errors on GraphQL errors.
|
|
12
|
-
errorPolicy: "all",
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe("resolver", () => {
|
|
17
|
-
const graphQLClient = createGraphQLClient();
|
|
18
|
-
|
|
19
|
-
describe("incrementUserAge", () => {
|
|
20
|
-
const uuid = randomUUID();
|
|
21
|
-
|
|
22
|
-
test("prepare data", async () => {
|
|
23
|
-
const query = gql`
|
|
24
|
-
mutation {
|
|
25
|
-
createUser(input: {
|
|
26
|
-
name: "alice"
|
|
27
|
-
email: "alice-${uuid}@example.com"
|
|
28
|
-
age: 30
|
|
29
|
-
}) {
|
|
30
|
-
id
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
`;
|
|
34
|
-
const result = await graphQLClient.rawRequest(query);
|
|
35
|
-
expect(result.errors).toBeUndefined();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("basic functionality", async () => {
|
|
39
|
-
const query = gql`
|
|
40
|
-
mutation {
|
|
41
|
-
incrementUserAge(email: "alice-${uuid}@example.com") {
|
|
42
|
-
oldAge
|
|
43
|
-
newAge
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
const result = await graphQLClient.rawRequest(query);
|
|
48
|
-
expect(result.errors).toBeUndefined();
|
|
49
|
-
expect(result.data).toEqual({
|
|
50
|
-
incrementUserAge: {
|
|
51
|
-
oldAge: 30,
|
|
52
|
-
newAge: 31,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
-
import { startWorkflow } from "@tailor-platform/sdk/cli";
|
|
4
|
-
import config from "../tailor.config";
|
|
5
|
-
import simpleCalculationWorkflow from "../src/workflow/simple";
|
|
6
|
-
import userProfileSyncWorkflow from "../src/workflow/wrapTailordb";
|
|
7
|
-
|
|
8
|
-
describe.concurrent("workflow", () => {
|
|
9
|
-
test("simple-calculation: execute workflow and verify success", { timeout: 120000 }, async () => {
|
|
10
|
-
const { executionId, wait } = await startWorkflow({
|
|
11
|
-
workflow: simpleCalculationWorkflow,
|
|
12
|
-
authInvoker: config.auth.invoker("admin"),
|
|
13
|
-
arg: { a: 2, b: 3 },
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
console.log(`[simple-calculation] Execution ID: ${executionId}`);
|
|
17
|
-
|
|
18
|
-
const result = await wait();
|
|
19
|
-
expect(result).toMatchObject({
|
|
20
|
-
workflowName: "simple-calculation",
|
|
21
|
-
status: "SUCCESS",
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("user-profile-sync: execute workflow and verify success", { timeout: 120000 }, async () => {
|
|
26
|
-
const uuid = randomUUID();
|
|
27
|
-
const testEmail = `workflow-test-${uuid}@example.com`;
|
|
28
|
-
|
|
29
|
-
const { executionId, wait } = await startWorkflow({
|
|
30
|
-
workflow: userProfileSyncWorkflow,
|
|
31
|
-
authInvoker: config.auth.invoker("admin"),
|
|
32
|
-
arg: {
|
|
33
|
-
name: "workflow-test-user",
|
|
34
|
-
email: testEmail,
|
|
35
|
-
age: 25,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log(`[user-profile-sync] Execution ID: ${executionId}`);
|
|
40
|
-
|
|
41
|
-
const result = await wait();
|
|
42
|
-
expect(result).toMatchObject({
|
|
43
|
-
workflowName: "user-profile-sync",
|
|
44
|
-
status: "SUCCESS",
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
-
import resolver from "./simple";
|
|
4
|
-
|
|
5
|
-
describe("add resolver", () => {
|
|
6
|
-
test("basic functionality", async () => {
|
|
7
|
-
const result = await resolver.body({
|
|
8
|
-
input: { left: 1, right: 2 },
|
|
9
|
-
user: unauthenticatedTailorUser,
|
|
10
|
-
env: {},
|
|
11
|
-
});
|
|
12
|
-
expect(result).toBe(3);
|
|
13
|
-
});
|
|
14
|
-
});
|