@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.
Files changed (248) hide show
  1. package/__tests__/_helpers/fake-store-database.ts +428 -0
  2. package/__tests__/_helpers/invoice-store.ts +382 -0
  3. package/__tests__/booking-calendar-feed-route.test.ts +494 -0
  4. package/__tests__/custom-node-result-taken-branch.test.ts +107 -0
  5. package/__tests__/data-api-date-parser.test.ts +45 -0
  6. package/__tests__/email-locale-senders.test.ts +3 -1
  7. package/__tests__/email-sender-name.test.ts +513 -0
  8. package/__tests__/graph-utils-topological-order.test.ts +155 -1
  9. package/__tests__/invoice-billing-details.test.ts +384 -0
  10. package/__tests__/invoice-data-access-replace.test.ts +236 -2
  11. package/__tests__/invoice-order-items-settle.test.ts +1 -1
  12. package/__tests__/invoice-payment-email.test.ts +191 -0
  13. package/__tests__/invoice-regenerate-email.test.ts +288 -0
  14. package/__tests__/invoice-regenerate-route.test.ts +9 -7
  15. package/__tests__/invoice-reservation.test.ts +1 -1
  16. package/__tests__/invoice-tender.test.ts +1 -1
  17. package/__tests__/middleware-admin-panel-roles.test.ts +127 -0
  18. package/__tests__/page-arrival-remount.test.ts +247 -0
  19. package/__tests__/payment-drivers-module.test.ts +19 -0
  20. package/__tests__/raw-query-error-code.test.ts +47 -0
  21. package/__tests__/runtime-template-token-resolution.test.ts +16 -0
  22. package/__tests__/segment-reply-changed-keys.test.ts +31 -0
  23. package/__tests__/segment-route-skipped-branch.test.ts +101 -0
  24. package/__tests__/segment-splitter-checkout-order.test.ts +306 -0
  25. package/__tests__/streaming-on-end-env-filter.test.ts +18 -18
  26. package/__tests__/streaming-on-end-order.test.ts +387 -0
  27. package/dist/cjs/account-delete-route-generator.d.ts +1 -0
  28. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  29. package/dist/cjs/account-delete-route-generator.js +2 -1
  30. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  31. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  32. package/dist/cjs/api-route-generator.js +23 -13
  33. package/dist/cjs/api-route-generator.js.map +1 -1
  34. package/dist/cjs/auth-generator.d.ts +1 -0
  35. package/dist/cjs/auth-generator.d.ts.map +1 -1
  36. package/dist/cjs/auth-generator.js +77 -28
  37. package/dist/cjs/auth-generator.js.map +1 -1
  38. package/dist/cjs/booking-calendar-feed-route-generator.d.ts +28 -0
  39. package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -0
  40. package/dist/cjs/booking-calendar-feed-route-generator.js +296 -0
  41. package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -0
  42. package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
  43. package/dist/cjs/data-api-route-generator.js +6 -1
  44. package/dist/cjs/data-api-route-generator.js.map +1 -1
  45. package/dist/cjs/executor-generator.d.ts.map +1 -1
  46. package/dist/cjs/executor-generator.js +39 -28
  47. package/dist/cjs/executor-generator.js.map +1 -1
  48. package/dist/cjs/graph-utils.d.ts +31 -1
  49. package/dist/cjs/graph-utils.d.ts.map +1 -1
  50. package/dist/cjs/graph-utils.js +112 -10
  51. package/dist/cjs/graph-utils.js.map +1 -1
  52. package/dist/cjs/index.d.ts +1 -1
  53. package/dist/cjs/index.d.ts.map +1 -1
  54. package/dist/cjs/index.js +2 -1
  55. package/dist/cjs/index.js.map +1 -1
  56. package/dist/cjs/invoice/api-routes-code.d.ts +12 -3
  57. package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
  58. package/dist/cjs/invoice/api-routes-code.js +152 -21
  59. package/dist/cjs/invoice/api-routes-code.js.map +1 -1
  60. package/dist/cjs/invoice/data-access-code.d.ts.map +1 -1
  61. package/dist/cjs/invoice/data-access-code.js +133 -21
  62. package/dist/cjs/invoice/data-access-code.js.map +1 -1
  63. package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
  64. package/dist/cjs/invoice/email-sender-code.js +15 -25
  65. package/dist/cjs/invoice/email-sender-code.js.map +1 -1
  66. package/dist/cjs/invoice/index.js +1 -1
  67. package/dist/cjs/invoice/index.js.map +1 -1
  68. package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
  69. package/dist/cjs/invoice/invoice-assembly-code.js +50 -6
  70. package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
  71. package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
  72. package/dist/cjs/invoice/invoice-html-code.js +13 -2
  73. package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
  74. package/dist/cjs/nodes/data/data-raw-query.d.ts.map +1 -1
  75. package/dist/cjs/nodes/data/data-raw-query.js +11 -1
  76. package/dist/cjs/nodes/data/data-raw-query.js.map +1 -1
  77. package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
  78. package/dist/cjs/nodes/email/email-mailgun.js +4 -2
  79. package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
  80. package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
  81. package/dist/cjs/nodes/email/email-postmark.js +5 -2
  82. package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
  83. package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
  84. package/dist/cjs/nodes/email/email-resend.js +5 -2
  85. package/dist/cjs/nodes/email/email-resend.js.map +1 -1
  86. package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
  87. package/dist/cjs/nodes/email/email-sendgrid.js +8 -2
  88. package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
  89. package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
  90. package/dist/cjs/nodes/general/general-custom-js.js +11 -0
  91. package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
  92. package/dist/cjs/nodes/storage/storage-local-remove.d.ts.map +1 -1
  93. package/dist/cjs/nodes/storage/storage-local-remove.js +2 -0
  94. package/dist/cjs/nodes/storage/storage-local-remove.js.map +1 -1
  95. package/dist/cjs/nodes/storage/storage-local-set.d.ts.map +1 -1
  96. package/dist/cjs/nodes/storage/storage-local-set.js +4 -0
  97. package/dist/cjs/nodes/storage/storage-local-set.js.map +1 -1
  98. package/dist/cjs/page-arrival.d.ts +10 -0
  99. package/dist/cjs/page-arrival.d.ts.map +1 -0
  100. package/dist/cjs/page-arrival.js +86 -0
  101. package/dist/cjs/page-arrival.js.map +1 -0
  102. package/dist/cjs/payments/index.d.ts +10 -4
  103. package/dist/cjs/payments/index.d.ts.map +1 -1
  104. package/dist/cjs/payments/index.js +18 -7
  105. package/dist/cjs/payments/index.js.map +1 -1
  106. package/dist/cjs/segment-splitter.d.ts.map +1 -1
  107. package/dist/cjs/segment-splitter.js +65 -27
  108. package/dist/cjs/segment-splitter.js.map +1 -1
  109. package/dist/cjs/transactional-email-code.d.ts.map +1 -1
  110. package/dist/cjs/transactional-email-code.js +32 -18
  111. package/dist/cjs/transactional-email-code.js.map +1 -1
  112. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  113. package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
  114. package/dist/cjs/workflow-component-plugin.js +35 -149
  115. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  116. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  117. package/dist/cjs/workflow-project-plugin.js +16 -4
  118. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  119. package/dist/cjs/workflow-scope.d.ts +19 -0
  120. package/dist/cjs/workflow-scope.d.ts.map +1 -0
  121. package/dist/cjs/workflow-scope.js +147 -0
  122. package/dist/cjs/workflow-scope.js.map +1 -0
  123. package/dist/esm/account-delete-route-generator.d.ts +1 -0
  124. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  125. package/dist/esm/account-delete-route-generator.js +2 -1
  126. package/dist/esm/account-delete-route-generator.js.map +1 -1
  127. package/dist/esm/api-route-generator.d.ts.map +1 -1
  128. package/dist/esm/api-route-generator.js +23 -13
  129. package/dist/esm/api-route-generator.js.map +1 -1
  130. package/dist/esm/auth-generator.d.ts +1 -0
  131. package/dist/esm/auth-generator.d.ts.map +1 -1
  132. package/dist/esm/auth-generator.js +77 -28
  133. package/dist/esm/auth-generator.js.map +1 -1
  134. package/dist/esm/booking-calendar-feed-route-generator.d.ts +28 -0
  135. package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -0
  136. package/dist/esm/booking-calendar-feed-route-generator.js +291 -0
  137. package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -0
  138. package/dist/esm/data-api-route-generator.d.ts.map +1 -1
  139. package/dist/esm/data-api-route-generator.js +6 -1
  140. package/dist/esm/data-api-route-generator.js.map +1 -1
  141. package/dist/esm/executor-generator.d.ts.map +1 -1
  142. package/dist/esm/executor-generator.js +39 -28
  143. package/dist/esm/executor-generator.js.map +1 -1
  144. package/dist/esm/graph-utils.d.ts +31 -1
  145. package/dist/esm/graph-utils.d.ts.map +1 -1
  146. package/dist/esm/graph-utils.js +109 -9
  147. package/dist/esm/graph-utils.js.map +1 -1
  148. package/dist/esm/index.d.ts +1 -1
  149. package/dist/esm/index.d.ts.map +1 -1
  150. package/dist/esm/index.js +1 -1
  151. package/dist/esm/index.js.map +1 -1
  152. package/dist/esm/invoice/api-routes-code.d.ts +12 -3
  153. package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
  154. package/dist/esm/invoice/api-routes-code.js +152 -21
  155. package/dist/esm/invoice/api-routes-code.js.map +1 -1
  156. package/dist/esm/invoice/data-access-code.d.ts.map +1 -1
  157. package/dist/esm/invoice/data-access-code.js +133 -21
  158. package/dist/esm/invoice/data-access-code.js.map +1 -1
  159. package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
  160. package/dist/esm/invoice/email-sender-code.js +15 -25
  161. package/dist/esm/invoice/email-sender-code.js.map +1 -1
  162. package/dist/esm/invoice/index.js +1 -1
  163. package/dist/esm/invoice/index.js.map +1 -1
  164. package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
  165. package/dist/esm/invoice/invoice-assembly-code.js +50 -6
  166. package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
  167. package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
  168. package/dist/esm/invoice/invoice-html-code.js +13 -2
  169. package/dist/esm/invoice/invoice-html-code.js.map +1 -1
  170. package/dist/esm/nodes/data/data-raw-query.d.ts.map +1 -1
  171. package/dist/esm/nodes/data/data-raw-query.js +11 -1
  172. package/dist/esm/nodes/data/data-raw-query.js.map +1 -1
  173. package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
  174. package/dist/esm/nodes/email/email-mailgun.js +4 -2
  175. package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
  176. package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
  177. package/dist/esm/nodes/email/email-postmark.js +5 -2
  178. package/dist/esm/nodes/email/email-postmark.js.map +1 -1
  179. package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
  180. package/dist/esm/nodes/email/email-resend.js +5 -2
  181. package/dist/esm/nodes/email/email-resend.js.map +1 -1
  182. package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
  183. package/dist/esm/nodes/email/email-sendgrid.js +8 -2
  184. package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
  185. package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
  186. package/dist/esm/nodes/general/general-custom-js.js +11 -0
  187. package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
  188. package/dist/esm/nodes/storage/storage-local-remove.d.ts.map +1 -1
  189. package/dist/esm/nodes/storage/storage-local-remove.js +2 -0
  190. package/dist/esm/nodes/storage/storage-local-remove.js.map +1 -1
  191. package/dist/esm/nodes/storage/storage-local-set.d.ts.map +1 -1
  192. package/dist/esm/nodes/storage/storage-local-set.js +4 -0
  193. package/dist/esm/nodes/storage/storage-local-set.js.map +1 -1
  194. package/dist/esm/page-arrival.d.ts +10 -0
  195. package/dist/esm/page-arrival.d.ts.map +1 -0
  196. package/dist/esm/page-arrival.js +81 -0
  197. package/dist/esm/page-arrival.js.map +1 -0
  198. package/dist/esm/payments/index.d.ts +10 -4
  199. package/dist/esm/payments/index.d.ts.map +1 -1
  200. package/dist/esm/payments/index.js +16 -6
  201. package/dist/esm/payments/index.js.map +1 -1
  202. package/dist/esm/segment-splitter.d.ts.map +1 -1
  203. package/dist/esm/segment-splitter.js +66 -28
  204. package/dist/esm/segment-splitter.js.map +1 -1
  205. package/dist/esm/transactional-email-code.d.ts.map +1 -1
  206. package/dist/esm/transactional-email-code.js +32 -18
  207. package/dist/esm/transactional-email-code.js.map +1 -1
  208. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  209. package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
  210. package/dist/esm/workflow-component-plugin.js +32 -146
  211. package/dist/esm/workflow-component-plugin.js.map +1 -1
  212. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  213. package/dist/esm/workflow-project-plugin.js +16 -4
  214. package/dist/esm/workflow-project-plugin.js.map +1 -1
  215. package/dist/esm/workflow-scope.d.ts +19 -0
  216. package/dist/esm/workflow-scope.d.ts.map +1 -0
  217. package/dist/esm/workflow-scope.js +142 -0
  218. package/dist/esm/workflow-scope.js.map +1 -0
  219. package/package.json +4 -4
  220. package/src/account-delete-route-generator.ts +3 -1
  221. package/src/api-route-generator.ts +23 -13
  222. package/src/auth-generator.ts +83 -26
  223. package/src/booking-calendar-feed-route-generator.ts +293 -0
  224. package/src/data-api-route-generator.ts +6 -1
  225. package/src/executor-generator.ts +39 -28
  226. package/src/graph-utils.ts +143 -11
  227. package/src/index.ts +1 -0
  228. package/src/invoice/api-routes-code.ts +166 -21
  229. package/src/invoice/data-access-code.ts +139 -21
  230. package/src/invoice/email-sender-code.ts +15 -25
  231. package/src/invoice/index.ts +1 -1
  232. package/src/invoice/invoice-assembly-code.ts +50 -6
  233. package/src/invoice/invoice-html-code.ts +13 -2
  234. package/src/nodes/data/data-raw-query.ts +11 -1
  235. package/src/nodes/email/email-mailgun.ts +11 -2
  236. package/src/nodes/email/email-postmark.ts +12 -2
  237. package/src/nodes/email/email-resend.ts +12 -2
  238. package/src/nodes/email/email-sendgrid.ts +15 -2
  239. package/src/nodes/general/general-custom-js.ts +10 -0
  240. package/src/nodes/storage/storage-local-remove.ts +4 -0
  241. package/src/nodes/storage/storage-local-set.ts +6 -0
  242. package/src/page-arrival.ts +115 -0
  243. package/src/payments/index.ts +18 -5
  244. package/src/segment-splitter.ts +90 -36
  245. package/src/transactional-email-code.ts +32 -21
  246. package/src/workflow-component-plugin.ts +43 -188
  247. package/src/workflow-project-plugin.ts +17 -4
  248. 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
+ }