@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.2
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/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
- package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
- package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
- package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/noResult.svg +42 -42
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +265 -265
- package/migrations/0029_automations.sql +14 -14
- package/migrations/0030_test_seeds.sql +125 -125
- package/package.json +2 -2
- package/src/auth/__fixtures__/in-memory-hash-provider.ts +17 -17
- package/src/auth/__fixtures__/static-token-service.ts +22 -22
- package/src/auth/bcrypt-hash-provider.ts +24 -24
- package/src/auth/constants.ts +14 -14
- package/src/auth/generate-refresh-token.test.ts +23 -23
- package/src/auth/hash-provider.test.ts +55 -55
- package/src/auth/jose-token-service.ts +62 -62
- package/src/auth/jwt-claims-passthrough.test.ts +87 -87
- package/src/auth/login.test.ts +100 -100
- package/src/auth/login.ts +78 -78
- package/src/auth/refresh.ts +11 -11
- package/src/auth/token-service.test.ts +90 -90
- package/src/factory.ts +370 -368
- package/src/features/automations/__tests__/action-executors.test.ts +255 -255
- package/src/features/automations/__tests__/automation-runner.test.ts +196 -196
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -60
- package/src/features/automations/__tests__/automations.handler.test.ts +264 -264
- package/src/features/automations/__tests__/automations.repository.test.ts +163 -163
- package/src/features/automations/__tests__/automations.schema.test.ts +224 -224
- package/src/features/automations/__tests__/context-resolver.test.ts +126 -126
- package/src/features/automations/__tests__/cron-runner.test.ts +267 -267
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -144
- package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -383
- package/src/features/automations/__tests__/template-grammar.test.ts +308 -308
- package/src/features/automations/__tests__/webhook.executor.test.ts +142 -142
- package/src/features/automations/__tests__/when-evaluator.test.ts +274 -274
- package/src/features/automations/__tests__/when-pushdown.test.ts +281 -281
- package/src/features/automations/action-executors/create-entry.executor.ts +27 -27
- package/src/features/automations/action-executors/edit-field.executor.ts +26 -26
- package/src/features/automations/action-executors/index.ts +37 -37
- package/src/features/automations/action-executors/send-mail.executor.ts +53 -53
- package/src/features/automations/action-executors/set-variable.executor.ts +159 -159
- package/src/features/automations/action-executors/webhook.executor.ts +67 -67
- package/src/features/automations/automation-runner.ts +85 -85
- package/src/features/automations/automation-runner.utils.ts +47 -47
- package/src/features/automations/automations.handler.ts +197 -197
- package/src/features/automations/automations.schema.ts +177 -177
- package/src/features/automations/context-resolver.ts +152 -152
- package/src/features/automations/cron-runner.ts +140 -140
- package/src/features/automations/cron-runner.utils.ts +44 -44
- package/src/features/automations/filter-translation.ts +46 -46
- package/src/features/automations/index.ts +16 -16
- package/src/features/automations/template-grammar.ts +245 -245
- package/src/features/automations/var-access-resolver.ts +140 -140
- package/src/features/automations/when-evaluator.ts +87 -87
- package/src/features/automations/when-pushdown.ts +57 -57
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -359
- package/src/features/backrefs/backrefs.handler.ts +168 -168
- package/src/features/backrefs/d1-backref.repository.ts +84 -84
- package/src/features/backrefs/index.ts +5 -5
- package/src/features/content/constants.ts +16 -16
- package/src/features/content/handlers/bulk.handler.ts +185 -185
- package/src/features/content/handlers/create.ts +129 -129
- package/src/features/content/handlers/delete.ts +62 -62
- package/src/features/content/handlers/facets.ts +49 -49
- package/src/features/content/handlers/get.ts +120 -120
- package/src/features/content/handlers/helpers.test.ts +180 -180
- package/src/features/content/handlers/helpers.ts +93 -93
- package/src/features/content/handlers/list.ts +174 -174
- package/src/features/content/handlers/update.ts +174 -174
- package/src/features/content/index.ts +26 -26
- package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
- package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
- package/src/features/dashboard-layout/index.ts +5 -0
- package/src/features/draft/draft.handler.ts +195 -195
- package/src/features/draft/draft.middleware.ts +66 -66
- package/src/features/draft/index.ts +5 -5
- package/src/features/email/email.provider.ts +42 -42
- package/src/features/email/email.service.ts +98 -98
- package/src/features/email/email.types.ts +119 -119
- package/src/features/email/index.ts +33 -33
- package/src/features/email/providers/resend.ts +67 -67
- package/src/features/email/providers/smtp.ts +55 -55
- package/src/features/email/templates/automation-mail.ts +19 -19
- package/src/features/email/templates/password-changed.ts +63 -63
- package/src/features/email/templates/password-reset.ts +68 -68
- package/src/features/email/templates/shell.ts +96 -96
- package/src/features/notifications/index.ts +5 -5
- package/src/features/notifications/notifications.handler.ts +105 -105
- package/src/features/password-reset/index.ts +19 -19
- package/src/features/password-reset/request.ts +92 -92
- package/src/features/password-reset/reset.ts +102 -102
- package/src/features/rotate-field/index.ts +5 -5
- package/src/features/rotate-field/rotate-field.handler.ts +132 -132
- package/src/features/rotate-field/rotate-field.schema.ts +17 -17
- package/src/features/schema/schema.handler.ts +131 -131
- package/src/features/seeds/index.ts +5 -5
- package/src/features/seeds/seeds.handler.test.ts +1199 -632
- package/src/features/seeds/seeds.handler.ts +558 -693
- package/src/features/settings/__tests__/settings.handler.test.ts +555 -555
- package/src/features/settings/settings.handler.ts +392 -392
- package/src/features/setup/index.ts +288 -204
- package/src/features/stats/index.ts +5 -5
- package/src/features/stats/stats.handler.ts +420 -420
- package/src/features/webhooks/index.ts +63 -63
- package/src/index.ts +65 -65
- package/src/media-utils.ts +82 -82
- package/src/middleware/auth-providers.middleware.test.ts +85 -0
- package/src/middleware/auth-providers.middleware.ts +45 -45
- package/src/middleware/observability.middleware.test.ts +93 -0
- package/src/middleware/observability.middleware.ts +70 -70
- package/src/middleware/rate-limit.middleware.ts +45 -45
- package/src/middleware/repository.middleware.ts +117 -114
- package/src/middleware/seed-registry.middleware.test.ts +97 -97
- package/src/middleware/seed-registry.middleware.ts +21 -21
- package/src/middleware/storage.middleware.ts +25 -25
- package/src/middleware.ts +45 -45
- package/src/public/access-policy.ts +27 -27
- package/src/public/api-key-middleware.ts +57 -57
- package/src/public/cache-utils.ts +38 -38
- package/src/public/entry-projection.ts +46 -46
- package/src/public/idempotency.ts +23 -23
- package/src/public/index.ts +16 -16
- package/src/public/problem-details.ts +124 -124
- package/src/public/public-add.ts +114 -114
- package/src/public/public-edit.ts +163 -163
- package/src/public/public-errors.ts +19 -19
- package/src/public/public-read.ts +63 -63
- package/src/public/public-routes.ts +88 -88
- package/src/public/query-builder.test.ts +224 -224
- package/src/public/query-builder.ts +156 -156
- package/src/public/rate-limit-middleware.ts +34 -34
- package/src/public/read-list.ts +54 -54
- package/src/public/read-single.ts +48 -48
- package/src/public/response-builder.ts +30 -30
- package/src/public/sanitize.ts +69 -69
- package/src/public/slug-utils.ts +18 -18
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -30
- package/src/rate-limit/cloudflare-rate-limiter.ts +15 -15
- package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -37
- package/src/rate-limit/in-memory-rate-limiter.ts +17 -17
- package/src/rate-limit/no-op-rate-limiter.test.ts +22 -22
- package/src/rate-limit/no-op-rate-limiter.ts +11 -11
- package/src/search-utils.test.ts +211 -211
- package/src/search-utils.ts +213 -213
- package/src/search.ts +65 -65
- package/src/shared/apply-policies.test.ts +81 -81
- package/src/shared/apply-policies.ts +67 -67
- package/src/shared/automations.repository.d1.ts +150 -150
- package/src/shared/background-notification-service.test.ts +62 -62
- package/src/shared/background-notification-service.ts +52 -52
- package/src/shared/base.repository.d1.ts +32 -32
- package/src/shared/content-utils.test.ts +165 -165
- package/src/shared/content-utils.ts +86 -86
- package/src/shared/content.repository.d1.test.ts +507 -458
- package/src/shared/content.repository.d1.ts +851 -851
- package/src/shared/d1-activity-log.repository.test.ts +140 -140
- package/src/shared/d1-activity-log.repository.ts +105 -105
- package/src/shared/d1-activity-logger.test.ts +86 -86
- package/src/shared/d1-activity-logger.ts +67 -67
- package/src/shared/d1-analytics.repository.test.ts +78 -78
- package/src/shared/d1-analytics.repository.ts +85 -85
- package/src/shared/d1-content-scan.repository.test.ts +80 -80
- package/src/shared/d1-content-scan.repository.ts +37 -37
- package/src/shared/d1-notification.repository.test.ts +128 -128
- package/src/shared/d1-notification.repository.ts +118 -118
- package/src/shared/d1-password-reset-token.repository.test.ts +81 -81
- package/src/shared/d1-password-reset-token.repository.ts +56 -56
- package/src/shared/d1-search.repository.test.ts +87 -87
- package/src/shared/d1-search.repository.ts +88 -88
- package/src/shared/d1-session.repository.test.ts +125 -125
- package/src/shared/d1-session.repository.ts +102 -102
- package/src/shared/d1-setup-checklist.repository.ts +36 -36
- package/src/shared/d1-user.repository.test.ts +151 -151
- package/src/shared/d1-user.repository.ts +119 -119
- package/src/shared/d1-widget.repository.test.ts +267 -221
- package/src/shared/d1-widget.repository.ts +385 -341
- package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
- package/src/shared/dashboard-layout.repository.d1.ts +58 -0
- package/src/shared/demo-data-sql.ts +216 -54
- package/src/shared/demo-data.repository.d1.ts +20 -20
- package/src/shared/execution-context-scheduler.ts +13 -13
- package/src/shared/fixed-clock.ts +25 -25
- package/src/shared/fts-sync.ts +8 -8
- package/src/shared/idempotency.repository.d1.test.ts +83 -83
- package/src/shared/idempotency.repository.d1.ts +60 -60
- package/src/shared/in-memory-activity-logger.ts +19 -19
- package/src/shared/in-memory-notification-service.ts +19 -19
- package/src/shared/in-memory-seed.repository.ts +58 -51
- package/src/shared/media.repository.d1.test.ts +107 -107
- package/src/shared/media.repository.d1.ts +68 -68
- package/src/shared/qstash-notification-service.test.ts +67 -67
- package/src/shared/qstash-notification-service.ts +55 -55
- package/src/shared/query-utils.ts +141 -141
- package/src/shared/request-utils.ts +26 -26
- package/src/shared/schema-mutator.d1.test.ts +142 -0
- package/src/shared/schema-mutator.d1.ts +64 -64
- package/src/shared/seed-layout.repository.d1.test.ts +114 -114
- package/src/shared/seed-layout.repository.d1.ts +62 -62
- package/src/shared/seed-registry-cache.test.ts +68 -68
- package/src/shared/seed-registry-cache.ts +49 -49
- package/src/shared/seed.repository.d1.test.ts +143 -143
- package/src/shared/seed.repository.d1.ts +98 -98
- package/src/shared/sequential-id-generator.ts +33 -33
- package/src/shared/site-settings.repository.d1.ts +53 -53
- package/src/shared/storage/factory.ts +41 -41
- package/src/shared/storage/s3-bucket.ts +195 -195
- package/src/shared/storage-utils.ts +40 -40
- package/src/shared/system-stats.repository.d1.test.ts +58 -58
- package/src/shared/system-stats.repository.d1.ts +48 -48
- package/src/types.ts +90 -87
- package/src/upload.ts +198 -198
- package/src/widget.test.ts +85 -0
- package/src/widget.ts +251 -212
- package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
- package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
-
// See LICENSE in the repository root for license terms.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest'
|
|
6
|
-
import type { Automation } from '@beechcms/core'
|
|
7
|
-
import { resolveAutomationContext, deriveEntryContext } from '../context-resolver'
|
|
8
|
-
import { parseTemplateKey } from '../template-grammar'
|
|
9
|
-
|
|
10
|
-
function makeAutomation(overrides: Partial<Automation> = {}): Automation {
|
|
11
|
-
return {
|
|
12
|
-
id: 'auto-1',
|
|
13
|
-
seed_slug: 'orders',
|
|
14
|
-
name: 'test',
|
|
15
|
-
enabled: true,
|
|
16
|
-
triggers: [{ event: 'cron' as const, cron: '* * * * *' }],
|
|
17
|
-
trigger_conditions: null,
|
|
18
|
-
actions: [],
|
|
19
|
-
created_at: 0,
|
|
20
|
-
updated_at: 0,
|
|
21
|
-
...overrides,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe('resolveAutomationContext', () => {
|
|
26
|
-
const triggerEntry = { id: 'e1', email: 'alice@example.com', product_id: 'p1' }
|
|
27
|
-
const batchEntries = [
|
|
28
|
-
{ id: 'e1', total: 10, email: 'alice@example.com' },
|
|
29
|
-
{ id: 'e2', total: 20, email: 'bob@example.com' },
|
|
30
|
-
{ id: 'e3', total: 30, email: 'carol@example.com' },
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
it('this scope resolves to triggering entry field', async () => {
|
|
34
|
-
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
35
|
-
expect(ctx.lookup(parseTemplateKey('this:email')!)).toBe('alice@example.com')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('this. dot notation resolves to triggering entry field', async () => {
|
|
39
|
-
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
40
|
-
expect(ctx.lookup(parseTemplateKey('email')!)).toBe('alice@example.com')
|
|
41
|
-
expect(ctx.lookup(parseTemplateKey('this:product_id')!)).toBe('p1')
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('batch:all:count matches batchEntries length', async () => {
|
|
45
|
-
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, batchEntries)
|
|
46
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:count')!)).toBe(3)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('unknown scope → undefined + onMissing fires', async () => {
|
|
50
|
-
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
51
|
-
const missing: string[] = []
|
|
52
|
-
const val = ctx.lookup(parseTemplateKey('unknownseed:lastone:email')!, (f) => missing.push(f))
|
|
53
|
-
expect(val).toBeUndefined()
|
|
54
|
-
expect(missing).toHaveLength(1)
|
|
55
|
-
expect(missing[0]).toBe('unknownseed')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('pluck truncates to 100 entries and appends truncation marker', async () => {
|
|
59
|
-
const rows = Array.from({ length: 150 }, (_, i) => ({ email: `u${i}@example.com` }))
|
|
60
|
-
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, rows)
|
|
61
|
-
const key = parseTemplateKey('batch:all:pluck:email')!
|
|
62
|
-
const result = ctx.lookup(key) as string
|
|
63
|
-
const values = result.split(', ')
|
|
64
|
-
expect(result.endsWith(' …')).toBe(true)
|
|
65
|
-
expect(values.length).toBe(100)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('sum on non-numeric field values returns 0 and fires onMissing for NaN', async () => {
|
|
69
|
-
const ctx = await resolveAutomationContext(
|
|
70
|
-
makeAutomation(),
|
|
71
|
-
triggerEntry,
|
|
72
|
-
[{ id: 'e1', amount: 'not-a-number' }, { id: 'e2', amount: 'also-not' }],
|
|
73
|
-
)
|
|
74
|
-
const missing: string[] = []
|
|
75
|
-
const result = ctx.lookup(parseTemplateKey('batch:all:sum:amount')!, (f) => missing.push(f))
|
|
76
|
-
expect(result).toBe(0)
|
|
77
|
-
expect(missing.length).toBeGreaterThan(0)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('aggregates on batch', () => {
|
|
81
|
-
const rows = [
|
|
82
|
-
{ id: 'e1', total: 10 },
|
|
83
|
-
{ id: 'e2', total: 20 },
|
|
84
|
-
{ id: 'e3', total: 30 },
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
async function makeCtx(batchRows: Record<string, unknown>[]) {
|
|
88
|
-
return resolveAutomationContext(makeAutomation(), batchRows[0] ?? null, batchRows)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
it('count', async () => {
|
|
92
|
-
const ctx = await makeCtx(rows)
|
|
93
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:count')!)).toBe(3)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('sum', async () => {
|
|
97
|
-
const ctx = await makeCtx(rows)
|
|
98
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:sum:total')!)).toBe(60)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('avg', async () => {
|
|
102
|
-
const ctx = await makeCtx(rows)
|
|
103
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:avg:total')!)).toBe(20)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('min', async () => {
|
|
107
|
-
const ctx = await makeCtx(rows)
|
|
108
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:min:total')!)).toBe(10)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('max', async () => {
|
|
112
|
-
const ctx = await makeCtx(rows)
|
|
113
|
-
expect(ctx.lookup(parseTemplateKey('batch:all:max:total')!)).toBe(30)
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
describe('deriveEntryContext', () => {
|
|
119
|
-
it('this scope returns new entry, batch scope delegates to base', async () => {
|
|
120
|
-
const batchRows = [{ id: 'e1', val: 1 }, { id: 'e2', val: 2 }]
|
|
121
|
-
const base = await resolveAutomationContext(makeAutomation(), batchRows[0] ?? null, batchRows)
|
|
122
|
-
const derived = deriveEntryContext(base, { id: 'e2', val: 2, email: 'derived@example.com' })
|
|
123
|
-
expect(derived.lookup(parseTemplateKey('this:email')!)).toBe('derived@example.com')
|
|
124
|
-
expect(derived.lookup(parseTemplateKey('batch:all:count')!)).toBe(2)
|
|
125
|
-
})
|
|
126
|
-
})
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import type { Automation } from '@beechcms/core'
|
|
7
|
+
import { resolveAutomationContext, deriveEntryContext } from '../context-resolver'
|
|
8
|
+
import { parseTemplateKey } from '../template-grammar'
|
|
9
|
+
|
|
10
|
+
function makeAutomation(overrides: Partial<Automation> = {}): Automation {
|
|
11
|
+
return {
|
|
12
|
+
id: 'auto-1',
|
|
13
|
+
seed_slug: 'orders',
|
|
14
|
+
name: 'test',
|
|
15
|
+
enabled: true,
|
|
16
|
+
triggers: [{ event: 'cron' as const, cron: '* * * * *' }],
|
|
17
|
+
trigger_conditions: null,
|
|
18
|
+
actions: [],
|
|
19
|
+
created_at: 0,
|
|
20
|
+
updated_at: 0,
|
|
21
|
+
...overrides,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('resolveAutomationContext', () => {
|
|
26
|
+
const triggerEntry = { id: 'e1', email: 'alice@example.com', product_id: 'p1' }
|
|
27
|
+
const batchEntries = [
|
|
28
|
+
{ id: 'e1', total: 10, email: 'alice@example.com' },
|
|
29
|
+
{ id: 'e2', total: 20, email: 'bob@example.com' },
|
|
30
|
+
{ id: 'e3', total: 30, email: 'carol@example.com' },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
it('this scope resolves to triggering entry field', async () => {
|
|
34
|
+
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
35
|
+
expect(ctx.lookup(parseTemplateKey('this:email')!)).toBe('alice@example.com')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('this. dot notation resolves to triggering entry field', async () => {
|
|
39
|
+
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
40
|
+
expect(ctx.lookup(parseTemplateKey('email')!)).toBe('alice@example.com')
|
|
41
|
+
expect(ctx.lookup(parseTemplateKey('this:product_id')!)).toBe('p1')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('batch:all:count matches batchEntries length', async () => {
|
|
45
|
+
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, batchEntries)
|
|
46
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:count')!)).toBe(3)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('unknown scope → undefined + onMissing fires', async () => {
|
|
50
|
+
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, [triggerEntry])
|
|
51
|
+
const missing: string[] = []
|
|
52
|
+
const val = ctx.lookup(parseTemplateKey('unknownseed:lastone:email')!, (f) => missing.push(f))
|
|
53
|
+
expect(val).toBeUndefined()
|
|
54
|
+
expect(missing).toHaveLength(1)
|
|
55
|
+
expect(missing[0]).toBe('unknownseed')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('pluck truncates to 100 entries and appends truncation marker', async () => {
|
|
59
|
+
const rows = Array.from({ length: 150 }, (_, i) => ({ email: `u${i}@example.com` }))
|
|
60
|
+
const ctx = await resolveAutomationContext(makeAutomation(), triggerEntry, rows)
|
|
61
|
+
const key = parseTemplateKey('batch:all:pluck:email')!
|
|
62
|
+
const result = ctx.lookup(key) as string
|
|
63
|
+
const values = result.split(', ')
|
|
64
|
+
expect(result.endsWith(' …')).toBe(true)
|
|
65
|
+
expect(values.length).toBe(100)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('sum on non-numeric field values returns 0 and fires onMissing for NaN', async () => {
|
|
69
|
+
const ctx = await resolveAutomationContext(
|
|
70
|
+
makeAutomation(),
|
|
71
|
+
triggerEntry,
|
|
72
|
+
[{ id: 'e1', amount: 'not-a-number' }, { id: 'e2', amount: 'also-not' }],
|
|
73
|
+
)
|
|
74
|
+
const missing: string[] = []
|
|
75
|
+
const result = ctx.lookup(parseTemplateKey('batch:all:sum:amount')!, (f) => missing.push(f))
|
|
76
|
+
expect(result).toBe(0)
|
|
77
|
+
expect(missing.length).toBeGreaterThan(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('aggregates on batch', () => {
|
|
81
|
+
const rows = [
|
|
82
|
+
{ id: 'e1', total: 10 },
|
|
83
|
+
{ id: 'e2', total: 20 },
|
|
84
|
+
{ id: 'e3', total: 30 },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
async function makeCtx(batchRows: Record<string, unknown>[]) {
|
|
88
|
+
return resolveAutomationContext(makeAutomation(), batchRows[0] ?? null, batchRows)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
it('count', async () => {
|
|
92
|
+
const ctx = await makeCtx(rows)
|
|
93
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:count')!)).toBe(3)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('sum', async () => {
|
|
97
|
+
const ctx = await makeCtx(rows)
|
|
98
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:sum:total')!)).toBe(60)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('avg', async () => {
|
|
102
|
+
const ctx = await makeCtx(rows)
|
|
103
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:avg:total')!)).toBe(20)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('min', async () => {
|
|
107
|
+
const ctx = await makeCtx(rows)
|
|
108
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:min:total')!)).toBe(10)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('max', async () => {
|
|
112
|
+
const ctx = await makeCtx(rows)
|
|
113
|
+
expect(ctx.lookup(parseTemplateKey('batch:all:max:total')!)).toBe(30)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('deriveEntryContext', () => {
|
|
119
|
+
it('this scope returns new entry, batch scope delegates to base', async () => {
|
|
120
|
+
const batchRows = [{ id: 'e1', val: 1 }, { id: 'e2', val: 2 }]
|
|
121
|
+
const base = await resolveAutomationContext(makeAutomation(), batchRows[0] ?? null, batchRows)
|
|
122
|
+
const derived = deriveEntryContext(base, { id: 'e2', val: 2, email: 'derived@example.com' })
|
|
123
|
+
expect(derived.lookup(parseTemplateKey('this:email')!)).toBe('derived@example.com')
|
|
124
|
+
expect(derived.lookup(parseTemplateKey('batch:all:count')!)).toBe(2)
|
|
125
|
+
})
|
|
126
|
+
})
|