@tailor-platform/create-sdk 1.19.0 → 1.21.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 +1 -1
- package/templates/executor/README.md +32 -0
- package/templates/{testing → executor}/eslint.config.js +0 -5
- package/templates/executor/package.json +30 -0
- 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 +30 -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/tsconfig.json +16 -0
- package/templates/resolver/user-defined.d.ts +18 -0
- package/templates/resolver/vitest.config.ts +15 -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 +25 -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/{testing → workflow}/package.json +3 -2
- 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 +148 -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/tsconfig.json +16 -0
- package/templates/workflow/user-defined.d.ts +15 -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
- /package/templates/{testing → workflow}/vitest.config.ts +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { WORKFLOW_TEST_ENV_KEY, unauthenticatedTailorUser } 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(
|
|
19
|
+
{ orderId: "order-1", amount: 100 },
|
|
20
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
21
|
+
);
|
|
22
|
+
expect(result).toEqual({ valid: true, orderId: "order-1" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("validateOrder rejects zero amount", () => {
|
|
26
|
+
expect(() =>
|
|
27
|
+
validateOrder.body(
|
|
28
|
+
{ orderId: "order-1", amount: 0 },
|
|
29
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
30
|
+
),
|
|
31
|
+
).toThrow("Order amount must be positive");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("processPayment returns transaction", () => {
|
|
35
|
+
const result = processPayment.body(
|
|
36
|
+
{ orderId: "order-1", amount: 100 },
|
|
37
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
38
|
+
);
|
|
39
|
+
expect(result).toEqual({
|
|
40
|
+
transactionId: "txn-order-1",
|
|
41
|
+
amount: 100,
|
|
42
|
+
status: "completed",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("sendConfirmation returns confirmation", () => {
|
|
47
|
+
const result = sendConfirmation.body(
|
|
48
|
+
{ orderId: "order-1", transactionId: "txn-1" },
|
|
49
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
50
|
+
);
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
orderId: "order-1",
|
|
53
|
+
transactionId: "txn-1",
|
|
54
|
+
confirmed: true,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("orchestration tests with mocked triggers", () => {
|
|
60
|
+
test("fulfillOrder chains all jobs", async () => {
|
|
61
|
+
vi.spyOn(validateOrder, "trigger").mockResolvedValue({
|
|
62
|
+
valid: true,
|
|
63
|
+
orderId: "order-1",
|
|
64
|
+
});
|
|
65
|
+
vi.spyOn(processPayment, "trigger").mockResolvedValue({
|
|
66
|
+
transactionId: "txn-order-1",
|
|
67
|
+
amount: 100,
|
|
68
|
+
status: "completed" as const,
|
|
69
|
+
});
|
|
70
|
+
vi.spyOn(sendConfirmation, "trigger").mockResolvedValue({
|
|
71
|
+
orderId: "order-1",
|
|
72
|
+
transactionId: "txn-order-1",
|
|
73
|
+
confirmed: true,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await fulfillOrder.body(
|
|
77
|
+
{ orderId: "order-1", amount: 100 },
|
|
78
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(validateOrder.trigger).toHaveBeenCalledWith({
|
|
82
|
+
orderId: "order-1",
|
|
83
|
+
amount: 100,
|
|
84
|
+
});
|
|
85
|
+
expect(processPayment.trigger).toHaveBeenCalledWith({
|
|
86
|
+
orderId: "order-1",
|
|
87
|
+
amount: 100,
|
|
88
|
+
});
|
|
89
|
+
expect(sendConfirmation.trigger).toHaveBeenCalledWith({
|
|
90
|
+
orderId: "order-1",
|
|
91
|
+
transactionId: "txn-order-1",
|
|
92
|
+
});
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
orderId: "order-1",
|
|
95
|
+
transactionId: "txn-order-1",
|
|
96
|
+
confirmed: true,
|
|
97
|
+
paymentStatus: "completed",
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("workflow.mainJob.body() chains all jobs", async () => {
|
|
102
|
+
vi.spyOn(validateOrder, "trigger").mockResolvedValue({
|
|
103
|
+
valid: true,
|
|
104
|
+
orderId: "order-2",
|
|
105
|
+
});
|
|
106
|
+
vi.spyOn(processPayment, "trigger").mockResolvedValue({
|
|
107
|
+
transactionId: "txn-order-2",
|
|
108
|
+
amount: 200,
|
|
109
|
+
status: "completed" as const,
|
|
110
|
+
});
|
|
111
|
+
vi.spyOn(sendConfirmation, "trigger").mockResolvedValue({
|
|
112
|
+
orderId: "order-2",
|
|
113
|
+
transactionId: "txn-order-2",
|
|
114
|
+
confirmed: true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const result = await workflow.mainJob.body(
|
|
118
|
+
{ orderId: "order-2", amount: 200 },
|
|
119
|
+
{ env: {}, user: unauthenticatedTailorUser },
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(result).toEqual({
|
|
123
|
+
orderId: "order-2",
|
|
124
|
+
transactionId: "txn-order-2",
|
|
125
|
+
confirmed: true,
|
|
126
|
+
paymentStatus: "completed",
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("integration tests with .trigger()", () => {
|
|
132
|
+
test("workflow.mainJob.trigger() executes all jobs", async () => {
|
|
133
|
+
vi.stubEnv(WORKFLOW_TEST_ENV_KEY, JSON.stringify({}));
|
|
134
|
+
|
|
135
|
+
const result = await workflow.mainJob.trigger({
|
|
136
|
+
orderId: "order-3",
|
|
137
|
+
amount: 300,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
orderId: "order-3",
|
|
142
|
+
transactionId: "txn-order-3",
|
|
143
|
+
confirmed: true,
|
|
144
|
+
paymentStatus: "completed",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -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,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
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
2
|
-
import { describe, expect, test, vi } from "vitest";
|
|
3
|
-
import { addNumbers, multiplyNumbers, calculate, getUserInfo } from "./simple";
|
|
4
|
-
|
|
5
|
-
describe("workflow jobs", () => {
|
|
6
|
-
describe("addNumbers job", () => {
|
|
7
|
-
test("adds two numbers", () => {
|
|
8
|
-
const result = addNumbers.body(
|
|
9
|
-
{ a: 2, b: 3 },
|
|
10
|
-
{
|
|
11
|
-
env: {},
|
|
12
|
-
user: unauthenticatedTailorUser,
|
|
13
|
-
},
|
|
14
|
-
);
|
|
15
|
-
expect(result).toBe(5);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("handles negative numbers", () => {
|
|
19
|
-
const result = addNumbers.body(
|
|
20
|
-
{ a: -5, b: 3 },
|
|
21
|
-
{
|
|
22
|
-
env: {},
|
|
23
|
-
user: unauthenticatedTailorUser,
|
|
24
|
-
},
|
|
25
|
-
);
|
|
26
|
-
expect(result).toBe(-2);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("multiplyNumbers job", () => {
|
|
31
|
-
test("multiplies two numbers", () => {
|
|
32
|
-
const result = multiplyNumbers.body(
|
|
33
|
-
{ x: 4, y: 5 },
|
|
34
|
-
{
|
|
35
|
-
env: {},
|
|
36
|
-
user: unauthenticatedTailorUser,
|
|
37
|
-
},
|
|
38
|
-
);
|
|
39
|
-
expect(result).toBe(20);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("calculate job", () => {
|
|
44
|
-
test("calculates (a + b) * a", async () => {
|
|
45
|
-
// Mock the trigger methods for dependent jobs
|
|
46
|
-
vi.spyOn(addNumbers, "trigger").mockResolvedValue(5); // 2 + 3 = 5
|
|
47
|
-
vi.spyOn(multiplyNumbers, "trigger").mockResolvedValue(10); // 5 * 2 = 10
|
|
48
|
-
|
|
49
|
-
const result = await calculate.body(
|
|
50
|
-
{ a: 2, b: 3 },
|
|
51
|
-
{
|
|
52
|
-
env: {},
|
|
53
|
-
user: unauthenticatedTailorUser,
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
expect(addNumbers.trigger).toHaveBeenCalledWith({ a: 2, b: 3 });
|
|
58
|
-
expect(multiplyNumbers.trigger).toHaveBeenCalledWith({ x: 5, y: 2 });
|
|
59
|
-
expect(result).toBe(10);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe("getUserInfo job", () => {
|
|
64
|
-
test("returns user info from context", () => {
|
|
65
|
-
const result = getUserInfo.body(undefined, {
|
|
66
|
-
env: {},
|
|
67
|
-
user: unauthenticatedTailorUser,
|
|
68
|
-
});
|
|
69
|
-
expect(result).toEqual({
|
|
70
|
-
userId: unauthenticatedTailorUser.id,
|
|
71
|
-
workspaceId: unauthenticatedTailorUser.workspaceId,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("returns custom user info", () => {
|
|
76
|
-
const customUser = {
|
|
77
|
-
...unauthenticatedTailorUser,
|
|
78
|
-
id: "user-123",
|
|
79
|
-
workspaceId: "ws-456",
|
|
80
|
-
};
|
|
81
|
-
const result = getUserInfo.body(undefined, {
|
|
82
|
-
env: {},
|
|
83
|
-
user: customUser,
|
|
84
|
-
});
|
|
85
|
-
expect(result).toEqual({ userId: "user-123", workspaceId: "ws-456" });
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createWorkflowJob, createWorkflow } from "@tailor-platform/sdk";
|
|
2
|
-
|
|
3
|
-
export const addNumbers = createWorkflowJob({
|
|
4
|
-
name: "add-numbers",
|
|
5
|
-
body: (input: { a: number; b: number }) => {
|
|
6
|
-
return input.a + input.b;
|
|
7
|
-
},
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export const multiplyNumbers = createWorkflowJob({
|
|
11
|
-
name: "multiply-numbers",
|
|
12
|
-
body: (input: { x: number; y: number }) => {
|
|
13
|
-
return input.x * input.y;
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const calculate = createWorkflowJob({
|
|
18
|
-
name: "calculate",
|
|
19
|
-
body: async (input: { a: number; b: number }) => {
|
|
20
|
-
const sum = await addNumbers.trigger({ a: input.a, b: input.b });
|
|
21
|
-
const product = await multiplyNumbers.trigger({ x: sum, y: input.a });
|
|
22
|
-
return product;
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export const getUserInfo = createWorkflowJob({
|
|
27
|
-
name: "get-user-info",
|
|
28
|
-
body: (_input: undefined, { user }) => {
|
|
29
|
-
return { userId: user.id, workspaceId: user.workspaceId };
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default createWorkflow({
|
|
34
|
-
name: "simple-calculation",
|
|
35
|
-
mainJob: calculate,
|
|
36
|
-
});
|