1- import { mkdtemp , mkdir , rm , writeFile } from "node:fs/promises" ;
21import { execFile } from "node:child_process" ;
3- import { tmpdir } from "node:os" ;
4- import { join , resolve } from "node:path" ;
2+ import { resolve } from "node:path" ;
53import { promisify } from "node:util" ;
64
7- import { ScanCommand } from "@aws-sdk/client-dynamodb" ;
5+ import { PutItemCommand , ScanCommand } from "@aws-sdk/client-dynamodb" ;
86
97import {
108 createPublisherTable ,
@@ -16,7 +14,56 @@ import type { DbTestContext } from "./dynamodb-local";
1614const execFileAsync = promisify ( execFile ) ;
1715
1816const repoRoot = resolve ( __dirname , "../../../.." ) ;
17+ const exampleConfigStoreRoot = resolve ( repoRoot , "tests/example-config-store" ) ;
18+ const bundlePath = resolve (
19+ repoRoot ,
20+ "packages/ddb-publisher/artifacts/ddb-publish/index.cjs" ,
21+ ) ;
1922const tableName = "supplier-config-it" ;
23+ const expectedRecords = [
24+ {
25+ pk : "ENTITY#channel" ,
26+ sk : "ID#letter" ,
27+ entity : "channel" ,
28+ id : "letter" ,
29+ } ,
30+ {
31+ pk : "ENTITY#volume-group" ,
32+ sk : "ID#vg-1" ,
33+ entity : "volume-group" ,
34+ id : "vg-1" ,
35+ } ,
36+ {
37+ pk : "ENTITY#letter-variant" ,
38+ sk : "ID#lv-1" ,
39+ entity : "letter-variant" ,
40+ id : "lv-1" ,
41+ } ,
42+ {
43+ pk : "ENTITY#pack-specification" ,
44+ sk : "ID#pack-spec-1" ,
45+ entity : "pack-specification" ,
46+ id : "pack-spec-1" ,
47+ } ,
48+ {
49+ pk : "ENTITY#supplier" ,
50+ sk : "ID#sup-1" ,
51+ entity : "supplier" ,
52+ id : "sup-1" ,
53+ } ,
54+ {
55+ pk : "ENTITY#supplier-allocation" ,
56+ sk : "ID#alloc-1" ,
57+ entity : "supplier-allocation" ,
58+ id : "alloc-1" ,
59+ } ,
60+ {
61+ pk : "ENTITY#supplier-pack" ,
62+ sk : "ID#sp-1" ,
63+ entity : "supplier-pack" ,
64+ id : "sp-1" ,
65+ } ,
66+ ] as const ;
2067
2168let context : DbTestContext | null = null ;
2269
@@ -29,38 +76,72 @@ async function run(
2976 return execFileAsync ( command , args , { cwd, env } ) ;
3077}
3178
32- function mockPackSpecification ( ) : Record < string , unknown > {
33- return {
34- id : "bau-standard-c5" ,
35- name : "BAU Standard Letter C5" ,
36- status : "PROD" ,
37- version : 1 ,
38- createdAt : "2023-01-01T00:00:00Z" ,
39- updatedAt : "2023-01-01T00:00:00Z" ,
40- billingId : "BILLING-BAU-C5-001" ,
41- constraints : {
42- sheets : {
43- value : 5 ,
44- operator : "LESS_THAN_OR_EQUAL" ,
45- } ,
46- } ,
47- postage : {
48- id : "ECONOMY" ,
49- size : "STANDARD" ,
50- deliveryDays : 3 ,
51- } ,
52- assembly : {
53- envelopeId : "envelope-nhs-c5-economy" ,
54- printColour : "BLACK" ,
79+ function activeContext ( ) : DbTestContext {
80+ if ( ! context ) {
81+ throw new Error ( "DynamoDB test context was not initialised." ) ;
82+ }
83+
84+ return context ;
85+ }
86+
87+ async function runBundle ( extraArgs : string [ ] = [ ] ) : Promise < {
88+ stdout : string ;
89+ stderr : string ;
90+ } > {
91+ return run (
92+ "node" ,
93+ [
94+ bundlePath ,
95+ "--source" ,
96+ exampleConfigStoreRoot ,
97+ "--env" ,
98+ "draft" ,
99+ "--table" ,
100+ tableName ,
101+ ...extraArgs ,
102+ ] ,
103+ repoRoot ,
104+ {
105+ ...process . env ,
106+ SUPPLIER_CONFIG_DDB_ENDPOINT_URL : activeContext ( ) . endpoint ,
55107 } ,
56- } ;
108+ ) ;
109+ }
110+
111+ async function scanTable ( ) {
112+ return activeContext ( ) . ddbClient . send ( new ScanCommand ( { TableName : tableName } ) ) ;
113+ }
114+
115+ function expectExpectedRecords (
116+ items : NonNullable < Awaited < ReturnType < typeof scanTable > > [ "Items" ] > ,
117+ ) : void {
118+ expect ( items ) . toHaveLength ( expectedRecords . length ) ;
119+
120+ for ( const expectedRecord of expectedRecords ) {
121+ const actual = items . find (
122+ ( item ) =>
123+ item . pk ?. S === expectedRecord . pk
124+ && item . sk ?. S === expectedRecord . sk ,
125+ ) ;
126+
127+ expect ( actual ) . toBeDefined ( ) ;
128+ expect ( actual ?. env ?. S ) . toBe ( "draft" ) ;
129+ expect ( actual ?. entity ?. S ) . toBe ( expectedRecord . entity ) ;
130+ expect ( actual ?. id ?. S ) . toBe ( expectedRecord . id ) ;
131+ }
57132}
58133
59134describe ( "publish action integration (DynamoDB Local)" , ( ) => {
60135 beforeAll ( async ( ) => {
61136 try {
62137 context = await setupDynamoDBContainer ( ) ;
63138 await createPublisherTable ( context , tableName ) ;
139+ await run ( "npm" , [
140+ "run" ,
141+ "bundle:release" ,
142+ "--workspace" ,
143+ "@supplier-config/ddb-publisher" ,
144+ ] ) ;
64145 } catch ( error ) {
65146 const reason = error instanceof Error ? error . message : String ( error ) ;
66147 throw new Error (
@@ -91,68 +172,65 @@ describe("publish action integration (DynamoDB Local)", () => {
91172 } ) ;
92173
93174 it ( "publishes records via the bundled action runtime into local DynamoDB" , async ( ) => {
94- if ( ! context ) {
95- throw new Error ( "DynamoDB test context was not initialised." ) ;
96- }
175+ await runBundle ( ) ;
176+
177+ const scanned = await scanTable ( ) ;
178+
179+ expect ( scanned . Items ) . toBeDefined ( ) ;
180+ expectExpectedRecords ( scanned . Items ?? [ ] ) ;
181+ } ) ;
97182
98- const tempRoot = await mkdtemp ( join ( tmpdir ( ) , "ddb-publish-config-" ) ) ;
183+ it ( "blocks a reload when extra active records exist, then allows it with --force" , async ( ) => {
184+ await activeContext ( ) . ddbClient . send (
185+ new PutItemCommand ( {
186+ TableName : tableName ,
187+ Item : {
188+ pk : { S : "ENTITY#supplier" } ,
189+ sk : { S : "ID#stale-supplier" } ,
190+ entity : { S : "supplier" } ,
191+ id : { S : "stale-supplier" } ,
192+ env : { S : "draft" } ,
193+ status : { S : "DRAFT" } ,
194+ name : { S : "Stale Supplier" } ,
195+ channelType : { S : "LETTER" } ,
196+ dailyCapacity : { N : "1" } ,
197+ } ,
198+ } ) ,
199+ ) ;
200+
201+ let blocked = false ;
99202
100203 try {
101- const entityDir = join ( tempRoot , "pack-specification" ) ;
102- await mkdir ( entityDir , { recursive : true } ) ;
103- await writeFile (
104- join ( entityDir , "bau-standard-c5.json" ) ,
105- JSON . stringify ( mockPackSpecification ( ) , null , 2 ) ,
106- "utf8" ,
107- ) ;
204+ await runBundle ( ) ;
205+ } catch ( error ) {
206+ blocked = true ;
207+ const stdout = String ( ( error as { stdout ?: string } ) . stdout ?? "" ) ;
208+ const stderr = String ( ( error as { stderr ?: string } ) . stderr ?? "" ) ;
209+ const combined = `${ stdout } \n${ stderr } ` ;
108210
109- await run ( "npm" , [
110- "run" ,
111- "bundle:release" ,
112- "--workspace" ,
113- "@supplier-config/ddb-publisher" ,
114- ] ) ;
211+ expect ( combined ) . toContain ( "Upload blocked" ) ;
212+ expect ( combined ) . toContain ( "ID#stale-supplier" ) ;
213+ }
115214
116- const bundlePath = join (
117- repoRoot ,
118- "packages/ddb-publisher/artifacts/ddb-publish/index.cjs" ,
119- ) ;
215+ expect ( blocked ) . toBe ( true ) ;
120216
121- await run (
122- "node" ,
123- [
124- bundlePath ,
125- "--source" ,
126- tempRoot ,
127- "--env" ,
128- "draft" ,
129- "--table" ,
130- tableName ,
131- ] ,
132- repoRoot ,
133- {
134- ...process . env ,
135- SUPPLIER_CONFIG_DDB_ENDPOINT_URL : context . endpoint ,
136- AWS_ACCESS_KEY_ID : "fakeMyKeyId" ,
137- AWS_SECRET_ACCESS_KEY : "fakeSecretAccessKey" ,
138- AWS_REGION : "eu-west-2" ,
139- } ,
140- ) ;
217+ await runBundle ( [ "--force" ] ) ;
141218
142- const scanned = await context . ddbClient . send (
143- new ScanCommand ( { TableName : tableName } ) ,
144- ) ;
219+ const scanned = await scanTable ( ) ;
145220
146- expect ( scanned . Items ) . toBeDefined ( ) ;
147- expect ( scanned . Items ) . toHaveLength ( 1 ) ;
221+ expect ( scanned . Items ) . toBeDefined ( ) ;
222+ expect ( scanned . Items ) . toHaveLength ( expectedRecords . length + 1 ) ;
223+ expectExpectedRecords (
224+ ( scanned . Items ?? [ ] ) . filter ( ( item ) => item . sk ?. S !== "ID#stale-supplier" ) ,
225+ ) ;
148226
149- const [ item ] = scanned . Items ?? [ ] ;
150- expect ( item ?. pk ?. S ) . toBe ( "ENTITY#pack-specification" ) ;
151- expect ( item ?. sk ?. S ) . toBe ( "ID#bau-standard-c5" ) ;
152- expect ( item ?. env ?. S ) . toBe ( "draft" ) ;
153- expect ( item ?. entity ?. S ) . toBe ( "pack-specification" ) ;
154- } finally {
155- await rm ( tempRoot , { recursive : true , force : true } ) ;
156- }
227+ const stale = scanned . Items ?. find (
228+ ( item ) =>
229+ item . pk ?. S === "ENTITY#supplier"
230+ && item . sk ?. S === "ID#stale-supplier" ,
231+ ) ;
232+
233+ expect ( stale ) . toBeDefined ( ) ;
234+ expect ( stale ?. status ?. S ) . toBe ( "DRAFT" ) ;
157235 } ) ;
158236} ) ;
0 commit comments