@teleporthq/teleport-plugin-next-workflows 0.43.75 → 0.44.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/__tests__/_helpers/fake-store-database.ts +428 -0
- package/__tests__/_helpers/invoice-store.ts +382 -0
- package/__tests__/booking-calendar-feed-route.test.ts +494 -0
- package/__tests__/custom-node-result-taken-branch.test.ts +107 -0
- package/__tests__/data-api-date-parser.test.ts +45 -0
- package/__tests__/email-locale-senders.test.ts +3 -1
- package/__tests__/email-sender-name.test.ts +513 -0
- package/__tests__/graph-utils-topological-order.test.ts +155 -1
- package/__tests__/invoice-billing-details.test.ts +384 -0
- package/__tests__/invoice-data-access-replace.test.ts +236 -2
- package/__tests__/invoice-order-items-settle.test.ts +1 -1
- package/__tests__/invoice-payment-email.test.ts +191 -0
- package/__tests__/invoice-regenerate-email.test.ts +288 -0
- package/__tests__/invoice-regenerate-route.test.ts +9 -7
- package/__tests__/invoice-reservation.test.ts +1 -1
- package/__tests__/invoice-tender.test.ts +1 -1
- package/__tests__/middleware-admin-panel-roles.test.ts +127 -0
- package/__tests__/page-arrival-remount.test.ts +247 -0
- package/__tests__/payment-drivers-module.test.ts +19 -0
- package/__tests__/raw-query-error-code.test.ts +47 -0
- package/__tests__/runtime-template-token-resolution.test.ts +16 -0
- package/__tests__/segment-reply-changed-keys.test.ts +31 -0
- package/__tests__/segment-route-skipped-branch.test.ts +101 -0
- package/__tests__/segment-splitter-checkout-order.test.ts +306 -0
- package/__tests__/streaming-on-end-env-filter.test.ts +18 -18
- package/__tests__/streaming-on-end-order.test.ts +387 -0
- package/dist/cjs/account-delete-route-generator.d.ts +1 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +2 -1
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +23 -13
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/auth-generator.d.ts +1 -0
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +77 -28
- package/dist/cjs/auth-generator.js.map +1 -1
- package/dist/cjs/booking-calendar-feed-route-generator.d.ts +28 -0
- package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -0
- package/dist/cjs/booking-calendar-feed-route-generator.js +296 -0
- package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -0
- package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
- package/dist/cjs/data-api-route-generator.js +6 -1
- package/dist/cjs/data-api-route-generator.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +39 -28
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/graph-utils.d.ts +31 -1
- package/dist/cjs/graph-utils.d.ts.map +1 -1
- package/dist/cjs/graph-utils.js +112 -10
- package/dist/cjs/graph-utils.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/invoice/api-routes-code.d.ts +12 -3
- package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/cjs/invoice/api-routes-code.js +152 -21
- package/dist/cjs/invoice/api-routes-code.js.map +1 -1
- package/dist/cjs/invoice/data-access-code.d.ts.map +1 -1
- package/dist/cjs/invoice/data-access-code.js +133 -21
- package/dist/cjs/invoice/data-access-code.js.map +1 -1
- package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/cjs/invoice/email-sender-code.js +15 -25
- package/dist/cjs/invoice/email-sender-code.js.map +1 -1
- package/dist/cjs/invoice/index.js +1 -1
- package/dist/cjs/invoice/index.js.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.js +50 -6
- package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.js +13 -2
- package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.js +11 -1
- package/dist/cjs/nodes/data/data-raw-query.js.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.js +4 -2
- package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.js +5 -2
- package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
- package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-resend.js +5 -2
- package/dist/cjs/nodes/email/email-resend.js.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.js +8 -2
- package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.js +11 -0
- package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-remove.js +2 -0
- package/dist/cjs/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.js +4 -0
- package/dist/cjs/nodes/storage/storage-local-set.js.map +1 -1
- package/dist/cjs/page-arrival.d.ts +10 -0
- package/dist/cjs/page-arrival.d.ts.map +1 -0
- package/dist/cjs/page-arrival.js +86 -0
- package/dist/cjs/page-arrival.js.map +1 -0
- package/dist/cjs/payments/index.d.ts +10 -4
- package/dist/cjs/payments/index.d.ts.map +1 -1
- package/dist/cjs/payments/index.js +18 -7
- package/dist/cjs/payments/index.js.map +1 -1
- package/dist/cjs/segment-splitter.d.ts.map +1 -1
- package/dist/cjs/segment-splitter.js +65 -27
- package/dist/cjs/segment-splitter.js.map +1 -1
- package/dist/cjs/transactional-email-code.d.ts.map +1 -1
- package/dist/cjs/transactional-email-code.js +32 -18
- package/dist/cjs/transactional-email-code.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-component-plugin.js +35 -149
- package/dist/cjs/workflow-component-plugin.js.map +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +16 -4
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/cjs/workflow-scope.d.ts +19 -0
- package/dist/cjs/workflow-scope.d.ts.map +1 -0
- package/dist/cjs/workflow-scope.js +147 -0
- package/dist/cjs/workflow-scope.js.map +1 -0
- package/dist/esm/account-delete-route-generator.d.ts +1 -0
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +2 -1
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +23 -13
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/auth-generator.d.ts +1 -0
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +77 -28
- package/dist/esm/auth-generator.js.map +1 -1
- package/dist/esm/booking-calendar-feed-route-generator.d.ts +28 -0
- package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -0
- package/dist/esm/booking-calendar-feed-route-generator.js +291 -0
- package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -0
- package/dist/esm/data-api-route-generator.d.ts.map +1 -1
- package/dist/esm/data-api-route-generator.js +6 -1
- package/dist/esm/data-api-route-generator.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +39 -28
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/graph-utils.d.ts +31 -1
- package/dist/esm/graph-utils.d.ts.map +1 -1
- package/dist/esm/graph-utils.js +109 -9
- package/dist/esm/graph-utils.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/invoice/api-routes-code.d.ts +12 -3
- package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/esm/invoice/api-routes-code.js +152 -21
- package/dist/esm/invoice/api-routes-code.js.map +1 -1
- package/dist/esm/invoice/data-access-code.d.ts.map +1 -1
- package/dist/esm/invoice/data-access-code.js +133 -21
- package/dist/esm/invoice/data-access-code.js.map +1 -1
- package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/esm/invoice/email-sender-code.js +15 -25
- package/dist/esm/invoice/email-sender-code.js.map +1 -1
- package/dist/esm/invoice/index.js +1 -1
- package/dist/esm/invoice/index.js.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.js +50 -6
- package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-html-code.js +13 -2
- package/dist/esm/invoice/invoice-html-code.js.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.js +11 -1
- package/dist/esm/nodes/data/data-raw-query.js.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.js +4 -2
- package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
- package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-postmark.js +5 -2
- package/dist/esm/nodes/email/email-postmark.js.map +1 -1
- package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-resend.js +5 -2
- package/dist/esm/nodes/email/email-resend.js.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.js +8 -2
- package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.js +11 -0
- package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
- package/dist/esm/nodes/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-remove.js +2 -0
- package/dist/esm/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.js +4 -0
- package/dist/esm/nodes/storage/storage-local-set.js.map +1 -1
- package/dist/esm/page-arrival.d.ts +10 -0
- package/dist/esm/page-arrival.d.ts.map +1 -0
- package/dist/esm/page-arrival.js +81 -0
- package/dist/esm/page-arrival.js.map +1 -0
- package/dist/esm/payments/index.d.ts +10 -4
- package/dist/esm/payments/index.d.ts.map +1 -1
- package/dist/esm/payments/index.js +16 -6
- package/dist/esm/payments/index.js.map +1 -1
- package/dist/esm/segment-splitter.d.ts.map +1 -1
- package/dist/esm/segment-splitter.js +66 -28
- package/dist/esm/segment-splitter.js.map +1 -1
- package/dist/esm/transactional-email-code.d.ts.map +1 -1
- package/dist/esm/transactional-email-code.js +32 -18
- package/dist/esm/transactional-email-code.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-component-plugin.js +32 -146
- package/dist/esm/workflow-component-plugin.js.map +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +16 -4
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/dist/esm/workflow-scope.d.ts +19 -0
- package/dist/esm/workflow-scope.d.ts.map +1 -0
- package/dist/esm/workflow-scope.js +142 -0
- package/dist/esm/workflow-scope.js.map +1 -0
- package/package.json +4 -4
- package/src/account-delete-route-generator.ts +3 -1
- package/src/api-route-generator.ts +23 -13
- package/src/auth-generator.ts +83 -26
- package/src/booking-calendar-feed-route-generator.ts +293 -0
- package/src/data-api-route-generator.ts +6 -1
- package/src/executor-generator.ts +39 -28
- package/src/graph-utils.ts +143 -11
- package/src/index.ts +1 -0
- package/src/invoice/api-routes-code.ts +166 -21
- package/src/invoice/data-access-code.ts +139 -21
- package/src/invoice/email-sender-code.ts +15 -25
- package/src/invoice/index.ts +1 -1
- package/src/invoice/invoice-assembly-code.ts +50 -6
- package/src/invoice/invoice-html-code.ts +13 -2
- package/src/nodes/data/data-raw-query.ts +11 -1
- package/src/nodes/email/email-mailgun.ts +11 -2
- package/src/nodes/email/email-postmark.ts +12 -2
- package/src/nodes/email/email-resend.ts +12 -2
- package/src/nodes/email/email-sendgrid.ts +15 -2
- package/src/nodes/general/general-custom-js.ts +10 -0
- package/src/nodes/storage/storage-local-remove.ts +4 -0
- package/src/nodes/storage/storage-local-set.ts +6 -0
- package/src/page-arrival.ts +115 -0
- package/src/payments/index.ts +18 -5
- package/src/segment-splitter.ts +90 -36
- package/src/transactional-email-code.ts +32 -21
- package/src/workflow-component-plugin.ts +43 -188
- package/src/workflow-project-plugin.ts +17 -4
- package/src/workflow-scope.ts +184 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import type { UIDLInvoiceSettings } from '@teleporthq/teleport-types'
|
|
2
|
+
import {
|
|
3
|
+
generateInvoiceGenerateRouteCode,
|
|
4
|
+
generateInvoiceRegenerateRouteCode,
|
|
5
|
+
} from '../../src/invoice/api-routes-code'
|
|
6
|
+
import { generateDataAccessCode, getRecordMappingCode } from '../../src/invoice/data-access-code'
|
|
7
|
+
import { generateEmailSenderCode } from '../../src/invoice/email-sender-code'
|
|
8
|
+
import { generateInvoiceAssemblyCode } from '../../src/invoice/invoice-assembly-code'
|
|
9
|
+
import { generatePdfGeneratorCode } from '../../src/invoice/pdf-generator-code'
|
|
10
|
+
import { generateSentEmailLogCode } from '../../src/sent-email-log/sent-email-log-code'
|
|
11
|
+
import { generateEmailLocaleModuleCode } from '../../src/email-locale/email-locale-code'
|
|
12
|
+
import {
|
|
13
|
+
INVOICE_TEST_APP_SECRET,
|
|
14
|
+
evaluateEmittedModule,
|
|
15
|
+
serverCallHeaders,
|
|
16
|
+
} from './load-invoice-route'
|
|
17
|
+
import { FakeStoreDatabase } from './fake-store-database'
|
|
18
|
+
import { AbortControllerStub } from './abort-controller-stub'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A store's invoicing, EXECUTED: the payment's `/api/invoices/generate`, the
|
|
22
|
+
* admin panel's `/api/invoices/[id]/regenerate`, the data access, the
|
|
23
|
+
* assembly, the PDF client, the invoice sender, the sent-email ledger and the
|
|
24
|
+
* locale module, all as emitted. Only what lies outside the store is faked:
|
|
25
|
+
* its Postgres database, the PDF service and the email provider.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type Row = Record<string, unknown>
|
|
29
|
+
|
|
30
|
+
export const ORDER_ID = '0b7a3c52-5d1e-4c55-9a51-3f0c2b7e9d11'
|
|
31
|
+
export const SECOND_ORDER_ID = '7d4f8e21-1c3b-4a9e-8f62-5b0d9c3a2e44'
|
|
32
|
+
export const BUYER = 'ana@buyer.test'
|
|
33
|
+
export const SECOND_BUYER = 'ion@buyer.test'
|
|
34
|
+
|
|
35
|
+
/** Invoice Email Delivery as the editor saves it: Postmark, a named sender, a Spanish copy. */
|
|
36
|
+
const EMAIL_DELIVERY = {
|
|
37
|
+
enabled: true,
|
|
38
|
+
provider: 'postmark',
|
|
39
|
+
fromEmail: 'shop@lumen.test',
|
|
40
|
+
fromName: 'Lumen Pottery',
|
|
41
|
+
subject: 'Invoice {{invoiceNumber}}',
|
|
42
|
+
body: '<p>Hi {{customerName}}, invoice {{invoiceNumber}} for {{totalAmount}}: {{invoiceUrl}}</p>',
|
|
43
|
+
localizedTemplates: {
|
|
44
|
+
es: {
|
|
45
|
+
subject: 'Factura {{invoiceNumber}}',
|
|
46
|
+
body: '<p>Hola {{customerName}}, factura {{invoiceNumber}} por {{totalAmount}}: {{invoiceUrl}}</p>',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
secretKeys: { serverToken: 'INVOICE_POSTMARK_TOKEN' },
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const settingsFor = (emailDelivery: boolean): UIDLInvoiceSettings =>
|
|
53
|
+
({
|
|
54
|
+
invoicePrefix: 'INV-',
|
|
55
|
+
defaultTaxRate: 0,
|
|
56
|
+
taxIncludedInPrice: false,
|
|
57
|
+
companyDetails: { companyName: 'Lumen Pottery SRL', companyEmail: 'shop@lumen.test' },
|
|
58
|
+
template: { document: null },
|
|
59
|
+
tables: { invoicesTable: 'teleport_invoices', invoiceItemsTable: 'teleport_invoice_items' },
|
|
60
|
+
emailDelivery: emailDelivery ? EMAIL_DELIVERY : { enabled: false },
|
|
61
|
+
} as unknown as UIDLInvoiceSettings)
|
|
62
|
+
|
|
63
|
+
const LOCALES = { locales: ['en', 'es', 'de'], defaultLocale: 'en' }
|
|
64
|
+
|
|
65
|
+
const ENV = {
|
|
66
|
+
NEXTAUTH_SECRET: INVOICE_TEST_APP_SECRET,
|
|
67
|
+
TELEPORT_DB_CONNECTION_STRING: 'postgres://store:secret@db.test:5432/store',
|
|
68
|
+
PDF_SERVICE_URL: 'https://pdf.test',
|
|
69
|
+
PDF_SERVICE_API_KEY: 'pdf-key',
|
|
70
|
+
INVOICE_POSTMARK_TOKEN: 'postmark-token',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface PostmarkMessage {
|
|
74
|
+
From: string
|
|
75
|
+
To: string
|
|
76
|
+
Subject: string
|
|
77
|
+
HtmlBody: string
|
|
78
|
+
Attachments: Array<{ Name: string; Content: string; ContentType: string }>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface RouteAnswer {
|
|
82
|
+
status: number
|
|
83
|
+
body: Row
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface Store {
|
|
87
|
+
db: FakeStoreDatabase
|
|
88
|
+
/** Every email handed to the provider, accepted or refused. */
|
|
89
|
+
emails: PostmarkMessage[]
|
|
90
|
+
provider: { refuses: boolean }
|
|
91
|
+
pdfService: { down: boolean; renders: number; hold: Promise<void> | null }
|
|
92
|
+
/** Runtime storage: when configured, a PDF gets a public URL. */
|
|
93
|
+
storage: { configured: boolean }
|
|
94
|
+
errors: string[]
|
|
95
|
+
/** The payment webhook's "Generate Invoice For Paid Order" node. */
|
|
96
|
+
pay: (orderId: string) => Promise<RouteAnswer>
|
|
97
|
+
/** A signed-in admin issuing the order's invoice by hand, through the same route. */
|
|
98
|
+
issueAsAdmin: (orderId: string) => Promise<RouteAnswer>
|
|
99
|
+
/** The admin panel's Regenerate. */
|
|
100
|
+
regenerate: (invoiceId: string) => Promise<RouteAnswer>
|
|
101
|
+
routeCode: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const stores: Store[] = []
|
|
105
|
+
|
|
106
|
+
export function seedPaidOrder(
|
|
107
|
+
db: FakeStoreDatabase,
|
|
108
|
+
orderId: string,
|
|
109
|
+
overrides: Row = {},
|
|
110
|
+
buyer: string = BUYER
|
|
111
|
+
): void {
|
|
112
|
+
db.insert('teleport_orders', {
|
|
113
|
+
id: orderId,
|
|
114
|
+
order_number: 'ORD-' + orderId.slice(0, 4),
|
|
115
|
+
status: 'confirmed',
|
|
116
|
+
payment_status: 'paid',
|
|
117
|
+
currency: 'EUR',
|
|
118
|
+
total_amount: 100,
|
|
119
|
+
billing_name: 'Ana Pop',
|
|
120
|
+
billing_email: buyer,
|
|
121
|
+
payment_method: 'stripe',
|
|
122
|
+
locale: 'es',
|
|
123
|
+
invoice_id: null,
|
|
124
|
+
invoice_number: null,
|
|
125
|
+
invoice_pdf_url: null,
|
|
126
|
+
...overrides,
|
|
127
|
+
})
|
|
128
|
+
db.insert('teleport_order_items', {
|
|
129
|
+
id: orderId + '-line-1',
|
|
130
|
+
order_id: orderId,
|
|
131
|
+
product_id: 'p1',
|
|
132
|
+
product_name: 'Lamp',
|
|
133
|
+
quantity: 2,
|
|
134
|
+
unit_price: 50,
|
|
135
|
+
total_price: 100,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Builds the store: its emitted modules over the fake database, PDF service and provider. */
|
|
140
|
+
export function openStore(options: { emailDelivery?: boolean } = {}): Store {
|
|
141
|
+
const settings = settingsFor(options.emailDelivery !== false)
|
|
142
|
+
const db = new FakeStoreDatabase()
|
|
143
|
+
const emails: PostmarkMessage[] = []
|
|
144
|
+
const provider = { refuses: false }
|
|
145
|
+
const pdfService = { down: false, renders: 0, hold: null as Promise<void> | null }
|
|
146
|
+
const errors: string[] = []
|
|
147
|
+
|
|
148
|
+
const postmark = {
|
|
149
|
+
ServerClient: function ServerClient() {
|
|
150
|
+
return {
|
|
151
|
+
sendEmail: async (message: PostmarkMessage) => {
|
|
152
|
+
emails.push(message)
|
|
153
|
+
if (provider.refuses) {
|
|
154
|
+
throw new Error('422 The From address is not a confirmed sender signature')
|
|
155
|
+
}
|
|
156
|
+
return { MessageID: 'pm-' + emails.length }
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const sources: Record<string, string> = {
|
|
163
|
+
'data-access': `${generateDataAccessCode(settings, 'postgresql', null)}\n${getRecordMappingCode(
|
|
164
|
+
'postgresql'
|
|
165
|
+
)}`,
|
|
166
|
+
'pdf-generator': generatePdfGeneratorCode(settings, null, {}),
|
|
167
|
+
'invoice-assembly': generateInvoiceAssemblyCode(settings),
|
|
168
|
+
'email-sender': generateEmailSenderCode(settings.emailDelivery),
|
|
169
|
+
'sent-email-log': generateSentEmailLogCode({ pgSupported: true }),
|
|
170
|
+
'email-locale': generateEmailLocaleModuleCode(LOCALES),
|
|
171
|
+
}
|
|
172
|
+
const loaded: Record<string, unknown> = {}
|
|
173
|
+
const storeRequire = ((id: string): unknown => {
|
|
174
|
+
if (id === 'pg') {
|
|
175
|
+
return db.pg
|
|
176
|
+
}
|
|
177
|
+
if (id === 'postmark') {
|
|
178
|
+
return postmark
|
|
179
|
+
}
|
|
180
|
+
if (id === 'next-auth/jwt') {
|
|
181
|
+
return { getToken: async () => ({ id: 'admin-1', role: 'admin' }) }
|
|
182
|
+
}
|
|
183
|
+
const name = id.split('/').pop() as string
|
|
184
|
+
if (sources[name] !== undefined) {
|
|
185
|
+
if (!(name in loaded)) {
|
|
186
|
+
loaded[name] = evaluateEmittedModule(sources[name], storeRequire)
|
|
187
|
+
}
|
|
188
|
+
return loaded[name]
|
|
189
|
+
}
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
191
|
+
return require(id)
|
|
192
|
+
}) as NodeRequire
|
|
193
|
+
|
|
194
|
+
type Handler = (req: unknown, res: unknown) => Promise<void>
|
|
195
|
+
const generateRoute = evaluateEmittedModule<Handler>(
|
|
196
|
+
generateInvoiceGenerateRouteCode(settings),
|
|
197
|
+
storeRequire
|
|
198
|
+
)
|
|
199
|
+
const routeCode = generateInvoiceRegenerateRouteCode(settings)
|
|
200
|
+
const regenerateRoute = evaluateEmittedModule<Handler>(routeCode, storeRequire)
|
|
201
|
+
|
|
202
|
+
const call = async (handler: Handler, req: Row): Promise<RouteAnswer> => {
|
|
203
|
+
const answer: RouteAnswer = { status: 0, body: {} }
|
|
204
|
+
await handler(req, {
|
|
205
|
+
status(code: number) {
|
|
206
|
+
answer.status = code
|
|
207
|
+
return this
|
|
208
|
+
},
|
|
209
|
+
json(value: Row) {
|
|
210
|
+
answer.body = value
|
|
211
|
+
return this
|
|
212
|
+
},
|
|
213
|
+
setHeader() {
|
|
214
|
+
return this
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
return answer
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// The merchant's browser: a German admin page, so the request's own
|
|
221
|
+
// language is not the buyer's.
|
|
222
|
+
const adminBrowserHeaders = {
|
|
223
|
+
host: 'shop.test',
|
|
224
|
+
'x-forwarded-proto': 'https',
|
|
225
|
+
'x-teleport-locale': 'de',
|
|
226
|
+
referer: 'https://shop.test/de/admin/invoices',
|
|
227
|
+
cookie: 'next-auth.session-token=abc; NEXT_LOCALE=de',
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const store: Store = {
|
|
231
|
+
db,
|
|
232
|
+
emails,
|
|
233
|
+
provider,
|
|
234
|
+
pdfService,
|
|
235
|
+
storage: { configured: false },
|
|
236
|
+
errors,
|
|
237
|
+
routeCode,
|
|
238
|
+
pay: (orderId) =>
|
|
239
|
+
call(generateRoute, {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: serverCallHeaders(),
|
|
242
|
+
body: { orderId, sendEmail: true },
|
|
243
|
+
}),
|
|
244
|
+
issueAsAdmin: (orderId) =>
|
|
245
|
+
call(generateRoute, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: adminBrowserHeaders,
|
|
248
|
+
body: { orderId, sendEmail: true },
|
|
249
|
+
}),
|
|
250
|
+
regenerate: (invoiceId) =>
|
|
251
|
+
call(regenerateRoute, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
query: { id: invoiceId },
|
|
254
|
+
headers: adminBrowserHeaders,
|
|
255
|
+
}),
|
|
256
|
+
}
|
|
257
|
+
stores.push(store)
|
|
258
|
+
return store
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The multipart body of a storage upload, kept readable for the fake fetch. */
|
|
262
|
+
class FormDataStub {
|
|
263
|
+
readonly entries: Array<{ name: string; filename?: string }> = []
|
|
264
|
+
|
|
265
|
+
append(name: string, _value: unknown, filename?: string): void {
|
|
266
|
+
this.entries.push({ name, filename })
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The fetch the store's server code makes: the PDF service, and its own storage upload. */
|
|
271
|
+
async function fakeFetch(url: string, init: { body?: unknown } = {}) {
|
|
272
|
+
const store = stores[stores.length - 1]
|
|
273
|
+
const target = String(url)
|
|
274
|
+
if (target === 'https://pdf.test/generate') {
|
|
275
|
+
const { pdfService } = store
|
|
276
|
+
if (pdfService.down) {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
status: 503,
|
|
280
|
+
headers: { get: (): string | null => null },
|
|
281
|
+
text: async () => 'Service Unavailable',
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
pdfService.renders += 1
|
|
285
|
+
if (pdfService.hold) {
|
|
286
|
+
await pdfService.hold
|
|
287
|
+
}
|
|
288
|
+
const request = JSON.parse(String(init.body || '{}'))
|
|
289
|
+
const bytes = Buffer.from('%PDF-1.4 ' + String(request.metadata?.invoiceNumber || ''))
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
status: 200,
|
|
293
|
+
headers: { get: (name: string) => (name === 'content-type' ? 'application/pdf' : null) },
|
|
294
|
+
arrayBuffer: async () =>
|
|
295
|
+
bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.length),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (target.endsWith('/api/runtime-storage/upload')) {
|
|
299
|
+
if (!store.storage.configured) {
|
|
300
|
+
// The PDF is then served from the database.
|
|
301
|
+
return { ok: false, status: 500, json: async () => ({ error: 'Storage is not configured' }) }
|
|
302
|
+
}
|
|
303
|
+
const file = (init.body as FormDataStub).entries.find((entry) => entry.name === 'file')
|
|
304
|
+
const fileName = String(file && file.filename)
|
|
305
|
+
return {
|
|
306
|
+
ok: true,
|
|
307
|
+
status: 200,
|
|
308
|
+
json: async () => ({
|
|
309
|
+
files: [{ id: 'file-' + fileName, url: 'https://cdn.test/' + fileName }],
|
|
310
|
+
}),
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
throw new Error('unexpected fetch: ' + target)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** The invoice the payment reserved for an order. */
|
|
317
|
+
export const invoiceOf = (store: Store, orderId: string): Row => {
|
|
318
|
+
const invoice = store.db.tables.teleport_invoices.find((row) => row.order_id === orderId)
|
|
319
|
+
if (!invoice) {
|
|
320
|
+
throw new Error('order ' + orderId + ' has no invoice')
|
|
321
|
+
}
|
|
322
|
+
return invoice
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The payment with the PDF service down: the order is paid, its invoice has no PDF. */
|
|
326
|
+
export async function payWhilePdfServiceIsDown(store: Store, orderId: string): Promise<Row> {
|
|
327
|
+
store.pdfService.down = true
|
|
328
|
+
const paid = await store.pay(orderId)
|
|
329
|
+
store.pdfService.down = false
|
|
330
|
+
expect(paid.status).toBe(500)
|
|
331
|
+
const invoice = invoiceOf(store, orderId)
|
|
332
|
+
expect(invoice.pdf_size_bytes).toBeNull()
|
|
333
|
+
return invoice
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export const historyLines = (store: Store): Row[] =>
|
|
337
|
+
store.db.tables.teleport_events.filter((row) => row.event_type === 'order-email-sent')
|
|
338
|
+
|
|
339
|
+
export const ledgerRows = (store: Store): Row[] => store.db.tables.teleport_sent_emails
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The deployment every store of the calling suite runs in: its environment,
|
|
343
|
+
* the fake fetch, the browser globals the uploader uses, and a console whose
|
|
344
|
+
* errors each store keeps (`store.errors`).
|
|
345
|
+
*/
|
|
346
|
+
export function runsAsTheStore(): void {
|
|
347
|
+
const savedEnv = { ...process.env }
|
|
348
|
+
const runtime = globalThis as unknown as Record<string, unknown>
|
|
349
|
+
const savedGlobals = {
|
|
350
|
+
fetch: runtime.fetch,
|
|
351
|
+
AbortController: runtime.AbortController,
|
|
352
|
+
Blob: runtime.Blob,
|
|
353
|
+
FormData: runtime.FormData,
|
|
354
|
+
}
|
|
355
|
+
let consoleSpies: jest.SpyInstance[] = []
|
|
356
|
+
|
|
357
|
+
beforeEach(() => {
|
|
358
|
+
Object.assign(process.env, ENV)
|
|
359
|
+
runtime.fetch = fakeFetch
|
|
360
|
+
runtime.AbortController = AbortControllerStub
|
|
361
|
+
runtime.Blob = class {}
|
|
362
|
+
runtime.FormData = FormDataStub
|
|
363
|
+
stores.length = 0
|
|
364
|
+
consoleSpies = [
|
|
365
|
+
jest.spyOn(console, 'info').mockImplementation(() => undefined),
|
|
366
|
+
jest.spyOn(console, 'warn').mockImplementation(() => undefined),
|
|
367
|
+
jest.spyOn(console, 'log').mockImplementation(() => undefined),
|
|
368
|
+
jest.spyOn(console, 'error').mockImplementation((...args: unknown[]) => {
|
|
369
|
+
const store = stores[stores.length - 1]
|
|
370
|
+
if (store) {
|
|
371
|
+
store.errors.push(args.map(String).join(' '))
|
|
372
|
+
}
|
|
373
|
+
}),
|
|
374
|
+
]
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
afterEach(() => {
|
|
378
|
+
consoleSpies.forEach((spy) => spy.mockRestore())
|
|
379
|
+
process.env = { ...savedEnv }
|
|
380
|
+
Object.assign(runtime, savedGlobals)
|
|
381
|
+
})
|
|
382
|
+
}
|