create-qpq-app 0.1.25 → 0.1.27
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/package.json +2 -2
- package/template/README.md +4 -0
- package/template/apps/qpqjs/account.qpq.ts +4 -0
- package/template/apps/qpqjs/bootstrap.qpq.ts +13 -2
- package/template/apps/qpqjs/packages/service-utils/src/defineQpqjsService.ts +4 -0
- package/template/apps/qpqjs/services/test/models/src/SmokeProbeRecord.ts +12 -0
- package/template/apps/qpqjs/services/test/models/src/SmokeRunSummary.ts +8 -1
- package/template/apps/qpqjs/services/test/models/src/SmokeTestStatus.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/infrastructure.ts +0 -6
- package/template/apps/qpqjs/services/test/service/src/smoke/config/defineSmoke.ts +8 -174
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/assert/askSmokePollForMarker.ts +44 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/assert/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/config/defineSmokeHarness.ts +115 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{constants → harness/constants}/index.ts +0 -2
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/index.ts +10 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/smokeRun/askExecuteSmokeTest.ts +6 -5
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/logic/smokeRun/askStartSmokeRun.ts +85 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/smokeRun/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/logic/smokeRun/listSmokeTests.ts +6 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/smokeRun/summarizeSmokeRun.ts +5 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{models → harness/models}/SmokeTestRequestedQueueEvent.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/models/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/queue/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/types/SmokeSuite.ts +13 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests → harness/types}/SmokeTestDefinition.ts +3 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/harness/types/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/index.ts +2 -9
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/config/defineEmailReceiveSmoke.ts +20 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/constants/SMOKE_EMAIL_RECEIVER.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/emailReceiveSmokeSuite.ts +15 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/entry/email/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/entry/email/onSmokeEmailReceived.ts +33 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/index.ts +5 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/logic/askRunEmailReceiveTest.ts +41 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/emailReceive/logic/smokeEmailMarkerId.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/config/defineEncryptedResourcesSmoke.ts +29 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/constants/SMOKE_CRYPTO_KEY.ts +5 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/constants/SMOKE_ENCRYPTED_PROBE_DRIVE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/constants/SMOKE_ENCRYPTED_PROBE_STORE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/constants/index.ts +3 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/encryptedResourcesSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/logic/askRunEncryptedResourcesTest.ts +61 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/encryptedResources/logic/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/config/defineEventBusSmoke.ts +21 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/constants/SMOKE_PROBE_EVENT_BUS.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/constants/SMOKE_PROBE_EVENT_QUEUE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/constants/SMOKE_PROBE_EVENT_TYPE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/constants/index.ts +3 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/entry/queue/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{queue → suites/eventBus/entry/queue}/onSmokeProbeEvent.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/eventBusSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/index.ts +6 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/logic/askRunEventBusTest.ts +31 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventBus/models/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/config/defineEventDocSmoke.ts +44 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{constants → suites/eventDoc/constants}/smokeEventDoc.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/entry/queue/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{queue → suites/eventDoc/entry/queue}/onSmokeEventDocAppend.ts +3 -3
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/eventDocSmokeSuite.ts +19 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/index.ts +7 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/askAssertContiguousEventIds.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/askAssertSmokeProbeDocState.ts +2 -2
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/askAwaitSmokeEventDocLog.ts +3 -2
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/askRunEventDocConcurrentAppendTest.ts +5 -5
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/askRunEventDocInterleavedBatchAppendTest.ts +4 -4
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/eventDoc/models/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/index.ts +17 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStore/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStore/keyValueStoreSmokeSuite.ts +14 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/crossService → suites/keyValueStore/logic}/askRunCrossServiceKeyValueStoreTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/keyValueStore → suites/keyValueStore/logic}/askRunKeyValueStoreTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStore/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/config/defineKeyValueStoreStreamSmoke.ts +30 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/constants/SMOKE_STREAM_PROBE_STORE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/entry/kvsStream/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/entry/kvsStream/onSmokeStreamRecord.ts +33 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/index.ts +5 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/keyValueStoreStreamSmokeSuite.ts +11 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/logic/askRunKeyValueStoreStreamTest.ts +69 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/keyValueStoreStream/logic/smokeStreamMarkerId.ts +8 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/noop/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/noop/noopSmokeSuite.ts +7 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/openApi/config/defineOpenApiSmoke.ts +13 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/openApi/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/openApi/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/openApi → suites/openApi/logic}/askRunEchoRoundTripTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/openApi → suites/openApi/logic}/askRunOpenApiDocumentTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/openApi/openApiSmokeSuite.ts +13 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/config/defineParameterSmoke.ts +11 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/constants/SMOKE_PROBE_PARAMETER.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/constants/SMOKE_PROBE_PARAMETER_VALUE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/constants/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/parameter → suites/parameter/logic}/askRunParameterTest.ts +3 -5
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/parameter/parameterSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/{tick/config/defineTick.ts → smoke/suites/schedule/config/defineScheduleSmoke.ts} +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/schedule/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/schedule/entry/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/schedule/index.ts +6 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/schedule → suites/schedule/logic}/askRunScheduleTest.ts +4 -4
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/schedule/scheduleSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/config/defineScopedKeyValueStoreSmoke.ts +19 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/constants/SMOKE_SCOPED_PROBE_STORE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/logic/askRunScopedKeyValueStoreTest.ts +256 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/logic/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedKeyValueStore/scopedKeyValueStoreSmokeSuite.ts +11 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/config/defineScopedStorageDriveSmoke.ts +12 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/constants/SMOKE_SCOPED_PROBE_DRIVE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/logic/askRunScopedStorageDriveTest.ts +116 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/logic/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/scopedStorageDrive/scopedStorageDriveSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/config/defineSecretSmoke.ts +8 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/constants/SMOKE_PROBE_SECRET.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/constants/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/index.ts +4 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/secret → suites/secret/logic}/askRunSecretTest.ts +2 -2
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/secret/secretSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/signingKey/config/defineSigningKeySmoke.ts +12 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/signingKey/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/signingKey/index.ts +3 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/crossService → suites/signingKey/logic}/askRunCrossServiceSigningKeyTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/signingKey → suites/signingKey/logic}/askRunSigningKeyTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/signingKey/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/signingKey/signingKeySmokeSuite.ts +16 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/smokeSuites.ts +46 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDrive/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/crossService → suites/storageDrive/logic}/askRunCrossServiceStorageDriveTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/{tests/storageDrive → suites/storageDrive/logic}/askRunStorageDriveTest.ts +1 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDrive/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDrive/storageDriveSmokeSuite.ts +14 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/config/defineStorageDriveEventSmoke.ts +27 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/config/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/constants/SMOKE_FILE_EVENT_DRIVE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/constants/SMOKE_SCOPED_FILE_EVENT_DRIVE.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/constants/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/entry/storageDrive/index.ts +1 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/entry/storageDrive/onSmokeFileEvent.ts +38 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/index.ts +5 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/logic/askRunStorageDriveEventTest.ts +78 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/logic/index.ts +2 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/logic/smokeFileEventMarkerId.ts +14 -0
- package/template/apps/qpqjs/services/test/service/src/smoke/suites/storageDriveEvent/storageDriveEventSmokeSuite.ts +9 -0
- package/template/apps/qpqjs/services/testa/models/src/CrossServiceProbePayload.ts +1 -1
- package/template/apps/qpqjs/services/testa/service/src/infrastructure.ts +2 -2
- package/template/apps/qpqjs/services/testa/service/src/smoke/config/defineSmokeProbes.ts +16 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/config/index.ts +1 -0
- package/template/apps/qpqjs/services/{test/service/src/tick → testa/service/src/smoke}/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/index.ts +3 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/keyValueStore/config/defineKeyValueStoreSmokeProbe.ts +33 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/keyValueStore/config/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/keyValueStore/entry/serviceFunction/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/keyValueStore/logic/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/config/defineSigningKeySmokeProbe.ts +28 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/config/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/entry/serviceFunction/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/index.ts +3 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/signingKey/logic/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/config/defineStorageDriveSmokeProbe.ts +31 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/config/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/entry/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/entry/serviceFunction/index.ts +1 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/index.ts +3 -0
- package/template/apps/qpqjs/services/testa/service/src/smoke/suites/storageDrive/logic/index.ts +1 -0
- package/template/docusaurus/docs/actions/core/file/ask-file-write-text-contents.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-delete.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-get-all.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-get.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-query.md +3 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-scan.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-update.md +1 -1
- package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-upsert.md +1 -1
- package/template/docusaurus/docs/actions/core/platform/ask-platform-get-name.md +32 -0
- package/template/docusaurus/docs/actions/webserver/email/ask-email-parse.md +54 -0
- package/template/docusaurus/docs/config/config-aws/account-email-receiving.md +33 -0
- package/template/docusaurus/docs/config/core/crypto-key.md +3 -1
- package/template/docusaurus/docs/config/core/key-value-store.md +16 -5
- package/template/docusaurus/docs/config/core/storage-drive.md +5 -4
- package/template/docusaurus/docs/config/features/event-doc-routes.md +1 -0
- package/template/docusaurus/docs/config/features/event-doc.md +22 -1
- package/template/docusaurus/docs/config/features/tenant-stores.md +10 -5
- package/template/docusaurus/docs/config/features/tenant.md +36 -12
- package/template/docusaurus/docs/config/features/tenanted-email-receiver.md +79 -0
- package/template/docusaurus/docs/config/features/tenanted-event-doc.md +22 -7
- package/template/docusaurus/docs/config/webserver/email-receiver.md +65 -0
- package/template/docusaurus/docs/config/webserver/email-receiving-domain.md +41 -0
- package/template/scripts/smoke/runSmokeRun.mjs +1 -1
- package/template/apps/qpqjs/services/test/service/src/echo/config/defineEcho.ts +0 -9
- package/template/apps/qpqjs/services/test/service/src/smoke/constants/smokeProbe.ts +0 -14
- package/template/apps/qpqjs/services/test/service/src/smoke/logic/smokeRun/askStartSmokeRun.ts +0 -70
- package/template/apps/qpqjs/services/test/service/src/smoke/models/index.ts +0 -5
- package/template/apps/qpqjs/services/test/service/src/smoke/queue/index.ts +0 -3
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/crossService/index.ts +0 -3
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventBus/askRunEventBusTest.ts +0 -57
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/index.ts +0 -13
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/keyValueStore/index.ts +0 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/signingKey/index.ts +0 -1
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/smokeTestRegistry.ts +0 -54
- package/template/apps/qpqjs/services/test/service/src/smoke/tests/storageDrive/index.ts +0 -1
- package/template/apps/qpqjs/services/test/service/src/tick/config/index.ts +0 -1
- package/template/apps/qpqjs/services/testa/service/src/crossServiceProbe/config/defineCrossServiceProbe.ts +0 -68
- package/template/apps/qpqjs/services/testa/service/src/crossServiceProbe/config/index.ts +0 -1
- package/template/apps/qpqjs/services/testa/service/src/crossServiceProbe/entry/serviceFunction/index.ts +0 -3
- package/template/apps/qpqjs/services/testa/service/src/crossServiceProbe/logic/index.ts +0 -3
- package/template/docusaurus/docs/config/config-aws/aws-kms-key.md +0 -100
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests → harness/assert}/askSmokeAssert.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{constants → harness/constants}/SMOKE_RUNS_STORE.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{constants → harness/constants}/githubOidc.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{constants → harness/constants}/smokeRunQueue.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{controller → harness/controller}/askGetSmokeRun.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{controller → harness/controller}/askRunSmokeTests.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{controller → harness/controller}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{data → harness/data}/askGetSmokeRunById.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{data → harness/data}/askRecordSmokeTestResult.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{data → harness/data}/askSaveSmokeRun.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{data → harness/data}/askSetSmokeRunOutcome.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{data → harness/data}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/github/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/github/verifyGithubOidcToken.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{logic → harness/logic}/smokeRun/askFindSmokeRun.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{models → harness/models}/GithubOidcClaims.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{processors → harness/processors}/github/getGithubOidcAuthProcessors.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{processors → harness/processors}/github/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{processors → harness/processors}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{queue → harness/queue}/onSmokeTestRequested.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventBus → suites/eventBus/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{models → suites/eventBus/models}/SmokeProbeEventQueueEvent.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{eventDoc → suites/eventDoc/definition}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{eventDoc → suites/eventDoc/definition}/smokeProbeDocDefinition.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/eventDoc → suites/eventDoc/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{models → suites/eventDoc/models}/SmokeEventDocAppendQueueEvent.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{models → suites/eventDoc/models}/SmokeEventDocMark.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/noop → suites/noop/logic}/askRunNoopTest.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/noop → suites/noop/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{echo → smoke/suites/openApi}/controller/echo.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{echo → smoke/suites/openApi}/controller/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/openApi → suites/openApi/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/parameter → suites/parameter/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/constants/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/constants/scheduleTickName.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/constants/scheduleTickStore.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/entry/kvsStream/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/entry/kvsStream/onScheduleTickStream.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/entry/schedule/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/entry/schedule/onTick.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/schedule → suites/schedule/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/models/ScheduleTickRecord.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/{tick → smoke/suites/schedule}/models/index.ts +0 -0
- /package/template/apps/qpqjs/services/test/service/src/smoke/{tests/secret → suites/secret/logic}/index.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/keyValueStore}/entry/index.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/keyValueStore}/entry/serviceFunction/smokeCrossServiceKeyValueStoreProbe.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/keyValueStore}/index.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/keyValueStore}/logic/askProbeForeignKeyValueStore.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/signingKey}/entry/serviceFunction/smokeCrossServiceSigningKeyProbe.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/signingKey}/logic/askProbeForeignSigningKey.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/storageDrive}/entry/serviceFunction/smokeCrossServiceStorageDriveProbe.ts +0 -0
- /package/template/apps/qpqjs/services/testa/service/src/{crossServiceProbe → smoke/suites/storageDrive}/logic/askProbeForeignStorageDrive.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-qpq-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Scaffold a new quidproquo app: npx create-qpq-app my-app",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^22.13.13",
|
|
58
|
-
"quidproquo-tsconfig": "0.1.
|
|
58
|
+
"quidproquo-tsconfig": "0.1.27"
|
|
59
59
|
},
|
|
60
60
|
"bin": {
|
|
61
61
|
"create-qpq-app": "./lib/commonjs/bin/createQpqApp.js"
|
package/template/README.md
CHANGED
|
@@ -103,3 +103,7 @@ npx qpq go # interactive aws deploy: pick app, services, stacks (inf
|
|
|
103
103
|
1. Create its `config`, `models`, `x-models`, `shared-logic`, `x-logic`, `service-utils`, and `service` packages.
|
|
104
104
|
2. Each lib gets a `package.json` with `"build": "tsc -b"` and the dist-pointing `main`, `types`, and `exports`; a `tsconfig.json` (extends the root base, **no paths**, references `./tsconfig.lib.json`); and a `tsconfig.lib.json` (`composite`, `outDir ./dist`, `rootDir .`, with `references` to its workspace deps' `tsconfig.lib.json`). There is no root-level list to touch, since `npm run build --workspaces` discovers the new lib automatically.
|
|
105
105
|
3. `service/` gets a `package.json` (`@qpqjs/<svc>-service`, `private`, `"build": "tsc -p tsconfig.app.json"`) and a `tsconfig.app.json`. No rspack.config.ts and no bundle script, because `qpq go` bundles it by convention. `npm run build` then picks it up automatically.
|
|
106
|
+
|
|
107
|
+
## Test
|
|
108
|
+
|
|
109
|
+
Adding a note to test build / deploy - this is a second test.
|
|
@@ -7,6 +7,7 @@ import { QPQConfig } from 'quidproquo';
|
|
|
7
7
|
import {
|
|
8
8
|
AwsDataStoreRemovalPolicy,
|
|
9
9
|
defineAccountBudget,
|
|
10
|
+
defineAccountEmailReceiving,
|
|
10
11
|
defineAccountGithubOidcProvider,
|
|
11
12
|
defineAccountSecurityServices,
|
|
12
13
|
defineAwsDataStoreRemovalPolicy,
|
|
@@ -21,6 +22,9 @@ export default (): QPQConfig => [
|
|
|
21
22
|
// GitHub Actions OIDC provider, trusted by every app's deploy role in this account.
|
|
22
23
|
defineAccountGithubOidcProvider(),
|
|
23
24
|
|
|
25
|
+
// The one active SES receipt rule set; every app's inbound-email rules live in it.
|
|
26
|
+
defineAccountEmailReceiving(),
|
|
27
|
+
|
|
24
28
|
defineAccountSecurityServices({
|
|
25
29
|
// GuardDuty is org-managed for this account
|
|
26
30
|
// (delegated admin 816650600287 auto-enables detectors - a stack-created one
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
// domain certificates. The identity plumbing (defineApplication +
|
|
3
3
|
// defineAwsServiceAccountInfo) is provided by quidproquo-deploy-awscdk's
|
|
4
4
|
// workspace CDK app; this fragment must not declare its own.
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
defineApi,
|
|
7
|
+
defineDns,
|
|
8
|
+
defineEmailReceivingDomain,
|
|
9
|
+
QPQConfig,
|
|
10
|
+
} from 'quidproquo';
|
|
6
11
|
import {
|
|
7
12
|
defineAwsGithubDeployRole,
|
|
8
13
|
defineBootstrapWaf,
|
|
@@ -17,8 +22,14 @@ import { QPQJS_DOMAINS, QpqjsServiceEnum } from '@qpqjs/constants';
|
|
|
17
22
|
export default ({ region }: QpqAppDeployContext): QPQConfig => [
|
|
18
23
|
defineDns(QPQJS_DOMAINS),
|
|
19
24
|
|
|
25
|
+
// inbox.<env>.<root>: verified with SES and MX-routed to it, for every defineEmailReceiver.
|
|
26
|
+
defineEmailReceivingDomain('inbox'),
|
|
27
|
+
|
|
20
28
|
// The role the deploy workflow assumes (ids from `gh api repos/qpqjs/quidproquo`).
|
|
21
|
-
defineAwsGithubDeployRole('qpqjs/quidproquo', {
|
|
29
|
+
defineAwsGithubDeployRole('qpqjs/quidproquo', {
|
|
30
|
+
ownerId: 314167689,
|
|
31
|
+
repositoryId: 571382961,
|
|
32
|
+
}),
|
|
22
33
|
|
|
23
34
|
defineBootstrapWaf({
|
|
24
35
|
rateLimits: [{ name: 'all-traffic', limit: 2000 }],
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
defineCache,
|
|
7
7
|
defineDefaultRouteOptions,
|
|
8
8
|
defineDns,
|
|
9
|
+
defineEmailReceivingDomain,
|
|
9
10
|
defineFrontendBundleOptions,
|
|
10
11
|
QPQConfig,
|
|
11
12
|
} from 'quidproquo';
|
|
@@ -59,6 +60,9 @@ export const defineQpqjsService = (
|
|
|
59
60
|
),
|
|
60
61
|
|
|
61
62
|
defineDns(QPQJS_DOMAINS),
|
|
63
|
+
// Same domain bootstrap.qpq.ts declares: bootstrap owns the records, a service's
|
|
64
|
+
// defineEmailReceiver resolves its recipients from it.
|
|
65
|
+
defineEmailReceivingDomain('inbox'),
|
|
62
66
|
|
|
63
67
|
defineAdminUserDirectory({
|
|
64
68
|
owner: {
|
|
@@ -5,4 +5,16 @@ export type SmokeProbeRecord = {
|
|
|
5
5
|
probeId: string;
|
|
6
6
|
category: string;
|
|
7
7
|
value: number;
|
|
8
|
+
|
|
9
|
+
// Set by the storage drive event and kvs stream handlers: what the event
|
|
10
|
+
// said its scope and path/key were, and (for file events) the ambient
|
|
11
|
+
// storage scope the handler ran under, so a test can prove both agree.
|
|
12
|
+
scope?: string;
|
|
13
|
+
ambientScope?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
// Set by the kvs stream handler: the attribute names on the record's image,
|
|
16
|
+
// so a test can prove no backend bookkeeping attribute reaches a handler.
|
|
17
|
+
imageAttributes?: string[];
|
|
18
|
+
// Set by the email handler: the provider's spf/dkim verdicts on the message.
|
|
19
|
+
authentication?: string;
|
|
8
20
|
};
|
|
@@ -4,9 +4,16 @@ import { z } from 'zod/v4';
|
|
|
4
4
|
// cannot drift from the entries themselves.
|
|
5
5
|
export const SmokeRunSummarySchema = z.object({
|
|
6
6
|
total: z.int().nonnegative().describe('Tests registered for the run'),
|
|
7
|
-
completed: z
|
|
7
|
+
completed: z
|
|
8
|
+
.int()
|
|
9
|
+
.nonnegative()
|
|
10
|
+
.describe('Tests that have passed, failed or been skipped'),
|
|
8
11
|
passed: z.int().nonnegative(),
|
|
9
12
|
failed: z.int().nonnegative(),
|
|
13
|
+
skipped: z
|
|
14
|
+
.int()
|
|
15
|
+
.nonnegative()
|
|
16
|
+
.describe('Deployed-only tests not run on this platform'),
|
|
10
17
|
});
|
|
11
18
|
|
|
12
19
|
export type SmokeRunSummary = z.infer<typeof SmokeRunSummarySchema>;
|
|
@@ -3,9 +3,7 @@ import { defineDevServerOptions } from 'quidproquo-dev-server/config';
|
|
|
3
3
|
import { QpqjsServiceEnum } from '@qpqjs/constants';
|
|
4
4
|
import { defineQpqjsService } from '@qpqjs/service-utils';
|
|
5
5
|
|
|
6
|
-
import { defineEcho } from './echo/config/defineEcho';
|
|
7
6
|
import { defineSmoke } from './smoke/config/defineSmoke';
|
|
8
|
-
import { defineTick } from './tick/config/defineTick';
|
|
9
7
|
|
|
10
8
|
export default [
|
|
11
9
|
defineDevServerOptions({ port: 3083 }),
|
|
@@ -18,8 +16,4 @@ export default [
|
|
|
18
16
|
),
|
|
19
17
|
|
|
20
18
|
defineSmoke(),
|
|
21
|
-
|
|
22
|
-
defineEcho(),
|
|
23
|
-
|
|
24
|
-
defineTick(),
|
|
25
19
|
];
|
|
@@ -1,176 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventBus,
|
|
3
|
-
defineKeyValueStore,
|
|
4
|
-
defineParameter,
|
|
5
|
-
defineQueue,
|
|
6
|
-
defineRoute,
|
|
7
|
-
defineSecret,
|
|
8
|
-
defineSigningKey,
|
|
9
|
-
defineStorageDrive,
|
|
10
|
-
QPQConfig,
|
|
11
|
-
QpqFunctionRuntime,
|
|
12
|
-
} from 'quidproquo';
|
|
13
|
-
import { defineEventDoc } from 'quidproquo-features';
|
|
1
|
+
import { QPQConfig } from 'quidproquo';
|
|
14
2
|
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
SMOKE_PROBE_DRIVE,
|
|
18
|
-
SMOKE_PROBE_SIGNING_KEY,
|
|
19
|
-
SMOKE_PROBE_STORE,
|
|
20
|
-
} from '@qpqjs/constants';
|
|
21
|
-
import {
|
|
22
|
-
SmokeProbeRecord,
|
|
23
|
-
SmokeRunStartedSchema,
|
|
24
|
-
SmokeRunWithSummarySchema,
|
|
25
|
-
} from '@qpqjs/test-models';
|
|
3
|
+
import { defineSmokeHarness } from '../harness/config/defineSmokeHarness';
|
|
4
|
+
import { smokeSuites } from '../suites/smokeSuites';
|
|
26
5
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from '../constants/smokeEventDoc';
|
|
33
|
-
import {
|
|
34
|
-
SMOKE_PROBE_EVENT_BUS,
|
|
35
|
-
SMOKE_PROBE_EVENT_QUEUE,
|
|
36
|
-
SMOKE_PROBE_EVENT_TYPE,
|
|
37
|
-
SMOKE_PROBE_PARAMETER,
|
|
38
|
-
SMOKE_PROBE_PARAMETER_VALUE,
|
|
39
|
-
SMOKE_PROBE_SECRET,
|
|
40
|
-
} from '../constants/smokeProbe';
|
|
41
|
-
import {
|
|
42
|
-
SMOKE_RUN_QUEUE,
|
|
43
|
-
SMOKE_TEST_REQUESTED_MESSAGE_TYPE,
|
|
44
|
-
} from '../constants/smokeRunQueue';
|
|
45
|
-
import { smokeProbeDocDefinition } from '../eventDoc/smokeProbeDocDefinition';
|
|
46
|
-
|
|
47
|
-
// Everything the smoke feature needs: the run store, the queue runs execute
|
|
48
|
-
// on, and the two routes. Runtimes are located relative to this file, so the
|
|
49
|
-
// module can move as a unit.
|
|
50
|
-
//
|
|
51
|
-
// The routes look public (no user directory, no api keys) but carry a
|
|
52
|
-
// per-runtime action processor override that swaps the route auth decode for
|
|
53
|
-
// the GitHub OIDC validator - so ONLY a GitHub Actions run of our repo,
|
|
54
|
-
// holding an OIDC token with the qpq-smoke audience and matching pinned
|
|
55
|
-
// claims, gets past the preamble. Everything else 401s, tokenless included.
|
|
56
|
-
export const defineSmoke = (): QPQConfig => {
|
|
57
|
-
const githubOidcAuth: QpqFunctionRuntime[] = [
|
|
58
|
-
{
|
|
59
|
-
basePath: __dirname,
|
|
60
|
-
relativePath: '../processors/github/getGithubOidcAuthProcessors',
|
|
61
|
-
functionName: 'getGithubOidcAuthProcessors',
|
|
62
|
-
},
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
return [
|
|
66
|
-
// One record per run, keyed by runId; the queue workers each write their
|
|
67
|
-
// own test's entry as it completes and the GET route polls it.
|
|
68
|
-
defineKeyValueStore(SMOKE_RUNS_STORE, 'runId'),
|
|
69
|
-
|
|
70
|
-
// One message per test per run. Not FIFO and no concurrency cap, so the
|
|
71
|
-
// tests of a run fan out and execute in parallel.
|
|
72
|
-
defineQueue(SMOKE_RUN_QUEUE, {
|
|
73
|
-
[SMOKE_TEST_REQUESTED_MESSAGE_TYPE]: {
|
|
74
|
-
basePath: __dirname,
|
|
75
|
-
relativePath: '../queue/onSmokeTestRequested',
|
|
76
|
-
functionName: 'onSmokeTestRequested',
|
|
77
|
-
},
|
|
78
|
-
}),
|
|
79
|
-
|
|
80
|
-
// Throwaway probe resources, one per kind of owned resource the tag-based
|
|
81
|
-
// IAM grants cover. The store's `category` index is what the KVS test
|
|
82
|
-
// queries through, proving the `table/*/index/*` half of the grant.
|
|
83
|
-
defineKeyValueStore<SmokeProbeRecord>(SMOKE_PROBE_STORE, 'probeId', [], {
|
|
84
|
-
indexes: ['category'],
|
|
85
|
-
}),
|
|
86
|
-
defineParameter(SMOKE_PROBE_PARAMETER, {
|
|
87
|
-
value: SMOKE_PROBE_PARAMETER_VALUE,
|
|
88
|
-
}),
|
|
89
|
-
defineSecret(SMOKE_PROBE_SECRET),
|
|
90
|
-
defineStorageDrive(SMOKE_PROBE_DRIVE),
|
|
91
|
-
// An owned RSA signing key: kms:Sign + kms:GetPublicKey through the
|
|
92
|
-
// alias-conditioned grant. testa declares the same key foreign (see
|
|
93
|
-
// defineCrossServiceProbe) for the cross-service verify test.
|
|
94
|
-
defineSigningKey(SMOKE_PROBE_SIGNING_KEY),
|
|
95
|
-
|
|
96
|
-
// Event bus test path: publish to the bus, the subscribed queue's entry
|
|
97
|
-
// writes a marker into the probe store, the test polls for it.
|
|
98
|
-
defineEventBus(SMOKE_PROBE_EVENT_BUS),
|
|
99
|
-
defineQueue(
|
|
100
|
-
SMOKE_PROBE_EVENT_QUEUE,
|
|
101
|
-
{
|
|
102
|
-
[SMOKE_PROBE_EVENT_TYPE]: {
|
|
103
|
-
basePath: __dirname,
|
|
104
|
-
relativePath: '../queue/onSmokeProbeEvent',
|
|
105
|
-
functionName: 'onSmokeProbeEvent',
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
{ eventBusSubscriptions: [SMOKE_PROBE_EVENT_BUS] }
|
|
109
|
-
),
|
|
110
|
-
|
|
111
|
-
// The event-doc probe collection: a full registered collection (stores, stream
|
|
112
|
-
// projector, dynamic functions, routes) so the smoke tests exercise event docs the
|
|
113
|
-
// way a real service does. NOTE the routes mount with no auth - the test service
|
|
114
|
-
// has no user directory - so the collection is world-writable; it holds nothing
|
|
115
|
-
// but throwaway probe docs. The queue fans a test's writers out one invocation
|
|
116
|
-
// each, so their appends race across lambdas.
|
|
117
|
-
defineEventDoc(
|
|
118
|
-
smokeProbeDocDefinition,
|
|
119
|
-
{
|
|
120
|
-
basePath: __dirname,
|
|
121
|
-
relativePath: '../eventDoc/smokeProbeDocDefinition',
|
|
122
|
-
functionName: 'smokeProbeDocDefinition',
|
|
123
|
-
},
|
|
124
|
-
{ basePath: SMOKE_EVENT_DOC_BASE_PATH }
|
|
125
|
-
),
|
|
126
|
-
defineQueue(SMOKE_EVENT_DOC_APPEND_QUEUE, {
|
|
127
|
-
[SMOKE_EVENT_DOC_APPEND_MESSAGE_TYPE]: {
|
|
128
|
-
basePath: __dirname,
|
|
129
|
-
relativePath: '../queue/onSmokeEventDocAppend',
|
|
130
|
-
functionName: 'onSmokeEventDocAppend',
|
|
131
|
-
},
|
|
132
|
-
}),
|
|
133
|
-
|
|
134
|
-
// The zod models double as the routes' published contract, flattened to
|
|
135
|
-
// JSON Schema here for the OpenAPI document at /v1/docs.
|
|
136
|
-
defineRoute(
|
|
137
|
-
'POST',
|
|
138
|
-
'/smoke/run',
|
|
139
|
-
{
|
|
140
|
-
basePath: __dirname,
|
|
141
|
-
relativePath: '../controller/askRunSmokeTests',
|
|
142
|
-
functionName: 'askRunSmokeTests',
|
|
143
|
-
actionProcessors: githubOidcAuth,
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
schema: {
|
|
147
|
-
summary: 'Start a smoke run',
|
|
148
|
-
description:
|
|
149
|
-
'Queues every registered smoke test and returns the run id to poll. Requires a GitHub Actions OIDC token for this repo.',
|
|
150
|
-
tags: ['smoke'],
|
|
151
|
-
responseJsonSchema: z.toJSONSchema(SmokeRunStartedSchema),
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
),
|
|
155
|
-
|
|
156
|
-
defineRoute(
|
|
157
|
-
'GET',
|
|
158
|
-
'/smoke/run/{runId}',
|
|
159
|
-
{
|
|
160
|
-
basePath: __dirname,
|
|
161
|
-
relativePath: '../controller/askGetSmokeRun',
|
|
162
|
-
functionName: 'askGetSmokeRun',
|
|
163
|
-
actionProcessors: githubOidcAuth,
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
schema: {
|
|
167
|
-
summary: 'Get a smoke run',
|
|
168
|
-
description:
|
|
169
|
-
'The stored run plus pass/fail counts. Poll until status leaves running.',
|
|
170
|
-
tags: ['smoke'],
|
|
171
|
-
responseJsonSchema: z.toJSONSchema(SmokeRunWithSummarySchema),
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
),
|
|
175
|
-
];
|
|
176
|
-
};
|
|
6
|
+
/** The whole smoke feature: the harness plus the resources of every registered suite. */
|
|
7
|
+
export const defineSmoke = (): QPQConfig => [
|
|
8
|
+
defineSmokeHarness(),
|
|
9
|
+
smokeSuites.map((suite) => suite.defineConfig?.() ?? []),
|
|
10
|
+
];
|
package/template/apps/qpqjs/services/test/service/src/smoke/harness/assert/askSmokePollForMarker.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
askDelay,
|
|
3
|
+
askKeyValueStoreDelete,
|
|
4
|
+
askKeyValueStoreGet,
|
|
5
|
+
AskResponse,
|
|
6
|
+
Nullable,
|
|
7
|
+
} from 'quidproquo';
|
|
8
|
+
|
|
9
|
+
import { SMOKE_PROBE_STORE } from '@qpqjs/constants';
|
|
10
|
+
import { SmokeProbeRecord } from '@qpqjs/test-models';
|
|
11
|
+
|
|
12
|
+
import { askSmokeAssert } from './askSmokeAssert';
|
|
13
|
+
|
|
14
|
+
const POLL_ATTEMPTS = 15;
|
|
15
|
+
const POLL_INTERVAL_MS = 2000;
|
|
16
|
+
|
|
17
|
+
// Fan-in for every async probe: polls the probe store for the marker row an
|
|
18
|
+
// event handler writes, fails the test if it never arrives, and removes it
|
|
19
|
+
// so a rerun starts clean. The default wait covers the dev server's file
|
|
20
|
+
// watcher settle window and S3 / stream delivery latency; a slower path
|
|
21
|
+
// (mail delivery) asks for more attempts.
|
|
22
|
+
export function* askSmokePollForMarker(
|
|
23
|
+
markerId: string,
|
|
24
|
+
via: string,
|
|
25
|
+
attempts: number = POLL_ATTEMPTS
|
|
26
|
+
): AskResponse<SmokeProbeRecord> {
|
|
27
|
+
let marker: Nullable<SmokeProbeRecord> = null;
|
|
28
|
+
for (let attempt = 0; attempt < attempts && !marker; attempt += 1) {
|
|
29
|
+
yield* askDelay(POLL_INTERVAL_MS);
|
|
30
|
+
marker = yield* askKeyValueStoreGet<SmokeProbeRecord>(
|
|
31
|
+
SMOKE_PROBE_STORE,
|
|
32
|
+
markerId
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
yield* askSmokeAssert(
|
|
37
|
+
!!marker,
|
|
38
|
+
`marker [${markerId}] never arrived via ${via}`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
yield* askKeyValueStoreDelete(SMOKE_PROBE_STORE, markerId);
|
|
42
|
+
|
|
43
|
+
return marker!;
|
|
44
|
+
}
|
package/template/apps/qpqjs/services/test/service/src/smoke/harness/config/defineSmokeHarness.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineKeyValueStore,
|
|
3
|
+
defineQueue,
|
|
4
|
+
defineRoute,
|
|
5
|
+
defineStorageDrive,
|
|
6
|
+
QPQConfig,
|
|
7
|
+
QpqFunctionRuntime,
|
|
8
|
+
} from 'quidproquo';
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod/v4';
|
|
11
|
+
import { SMOKE_PROBE_DRIVE, SMOKE_PROBE_STORE } from '@qpqjs/constants';
|
|
12
|
+
import {
|
|
13
|
+
SmokeProbeRecord,
|
|
14
|
+
SmokeRunStartedSchema,
|
|
15
|
+
SmokeRunWithSummarySchema,
|
|
16
|
+
} from '@qpqjs/test-models';
|
|
17
|
+
|
|
18
|
+
import { SMOKE_RUNS_STORE } from '../constants/SMOKE_RUNS_STORE';
|
|
19
|
+
import {
|
|
20
|
+
SMOKE_RUN_QUEUE,
|
|
21
|
+
SMOKE_TEST_REQUESTED_MESSAGE_TYPE,
|
|
22
|
+
} from '../constants/smokeRunQueue';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The part of the smoke feature every suite relies on: the run store, the
|
|
26
|
+
* queue runs execute on, the shared probe store and drive, and the two
|
|
27
|
+
* routes. Knows nothing about individual suites; defineSmoke adds theirs
|
|
28
|
+
* alongside.
|
|
29
|
+
*
|
|
30
|
+
* The routes look public (no user directory, no api keys) but carry a
|
|
31
|
+
* per-runtime action processor override that swaps the route auth decode for
|
|
32
|
+
* the GitHub OIDC validator, so ONLY a GitHub Actions run of our repo holding
|
|
33
|
+
* an OIDC token with the qpq-smoke audience and matching pinned claims gets
|
|
34
|
+
* past the preamble. Everything else 401s, tokenless included.
|
|
35
|
+
*/
|
|
36
|
+
export const defineSmokeHarness = (): QPQConfig => {
|
|
37
|
+
const githubOidcAuth: QpqFunctionRuntime[] = [
|
|
38
|
+
{
|
|
39
|
+
basePath: __dirname,
|
|
40
|
+
relativePath: '../processors/github/getGithubOidcAuthProcessors',
|
|
41
|
+
functionName: 'getGithubOidcAuthProcessors',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
// One record per run, keyed by runId; the queue workers each write their
|
|
47
|
+
// own test's entry as it completes and the GET route polls it.
|
|
48
|
+
defineKeyValueStore(SMOKE_RUNS_STORE, 'runId'),
|
|
49
|
+
|
|
50
|
+
// One message per test per run. Not FIFO and no concurrency cap, so the
|
|
51
|
+
// tests of a run fan out and execute in parallel.
|
|
52
|
+
defineQueue(SMOKE_RUN_QUEUE, {
|
|
53
|
+
[SMOKE_TEST_REQUESTED_MESSAGE_TYPE]: {
|
|
54
|
+
basePath: __dirname,
|
|
55
|
+
relativePath: '../queue/onSmokeTestRequested',
|
|
56
|
+
functionName: 'onSmokeTestRequested',
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// Every async suite's handler writes its marker here for
|
|
61
|
+
// askSmokePollForMarker. The keyValueStore suite queries its `category`
|
|
62
|
+
// index, proving the `table/*/index/*` half of the tag-conditioned grant,
|
|
63
|
+
// and testa reaches it cross-service.
|
|
64
|
+
defineKeyValueStore<SmokeProbeRecord>(SMOKE_PROBE_STORE, 'probeId', [], {
|
|
65
|
+
indexes: ['category'],
|
|
66
|
+
}),
|
|
67
|
+
|
|
68
|
+
// The unscoped probe drive: the storageDrive suite's subject, testa's
|
|
69
|
+
// cross-service target, and the drive the scopedStorageDrive suite proves
|
|
70
|
+
// refuses a scope.
|
|
71
|
+
defineStorageDrive(SMOKE_PROBE_DRIVE),
|
|
72
|
+
|
|
73
|
+
// The zod models double as the routes' published contract, flattened to
|
|
74
|
+
// JSON Schema here for the OpenAPI document at /v1/docs.
|
|
75
|
+
defineRoute(
|
|
76
|
+
'POST',
|
|
77
|
+
'/smoke/run',
|
|
78
|
+
{
|
|
79
|
+
basePath: __dirname,
|
|
80
|
+
relativePath: '../controller/askRunSmokeTests',
|
|
81
|
+
functionName: 'askRunSmokeTests',
|
|
82
|
+
actionProcessors: githubOidcAuth,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
schema: {
|
|
86
|
+
summary: 'Start a smoke run',
|
|
87
|
+
description:
|
|
88
|
+
'Queues every registered smoke test and returns the run id to poll. Requires a GitHub Actions OIDC token for this repo.',
|
|
89
|
+
tags: ['smoke'],
|
|
90
|
+
responseJsonSchema: z.toJSONSchema(SmokeRunStartedSchema),
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
defineRoute(
|
|
96
|
+
'GET',
|
|
97
|
+
'/smoke/run/{runId}',
|
|
98
|
+
{
|
|
99
|
+
basePath: __dirname,
|
|
100
|
+
relativePath: '../controller/askGetSmokeRun',
|
|
101
|
+
functionName: 'askGetSmokeRun',
|
|
102
|
+
actionProcessors: githubOidcAuth,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
schema: {
|
|
106
|
+
summary: 'Get a smoke run',
|
|
107
|
+
description:
|
|
108
|
+
'The stored run plus pass/fail counts. Poll until status leaves running.',
|
|
109
|
+
tags: ['smoke'],
|
|
110
|
+
responseJsonSchema: z.toJSONSchema(SmokeRunWithSummarySchema),
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
];
|
|
115
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './defineSmokeHarness';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './assert';
|
|
2
|
+
export * from './config';
|
|
3
|
+
export * from './constants';
|
|
4
|
+
export * from './controller';
|
|
5
|
+
export * from './data';
|
|
6
|
+
export * from './logic';
|
|
7
|
+
export * from './models';
|
|
8
|
+
export * from './processors';
|
|
9
|
+
export * from './queue';
|
|
10
|
+
export * from './types';
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
import { askGetSmokeRunById } from '../../data/askGetSmokeRunById';
|
|
17
17
|
import { askRecordSmokeTestResult } from '../../data/askRecordSmokeTestResult';
|
|
18
18
|
import { askSetSmokeRunOutcome } from '../../data/askSetSmokeRunOutcome';
|
|
19
|
-
import { SmokeTestDefinition } from '../../
|
|
20
|
-
import {
|
|
19
|
+
import { SmokeTestDefinition } from '../../types/SmokeTestDefinition';
|
|
20
|
+
import { listSmokeTests } from './listSmokeTests';
|
|
21
21
|
|
|
22
22
|
// Runs one registered test, returning its completed result entry.
|
|
23
23
|
function* askRunSmokeTest(
|
|
@@ -40,7 +40,8 @@ function* askRunSmokeTest(
|
|
|
40
40
|
|
|
41
41
|
const isTerminal = (result: SmokeTestResult): boolean =>
|
|
42
42
|
result.status === SmokeTestStatus.passed ||
|
|
43
|
-
result.status === SmokeTestStatus.failed
|
|
43
|
+
result.status === SmokeTestStatus.failed ||
|
|
44
|
+
result.status === SmokeTestStatus.skipped;
|
|
44
45
|
|
|
45
46
|
// Executes one registered test for an existing run (created by
|
|
46
47
|
// askStartSmokeRun) and records its result. Every test of a run executes
|
|
@@ -52,7 +53,7 @@ export function* askExecuteSmokeTest(
|
|
|
52
53
|
runId: string,
|
|
53
54
|
testName: string
|
|
54
55
|
): AskResponse<SmokeRun> {
|
|
55
|
-
const test =
|
|
56
|
+
const test = listSmokeTests().find((t) => t.name === testName);
|
|
56
57
|
if (!test) {
|
|
57
58
|
return yield* askThrowError(
|
|
58
59
|
ErrorTypeEnum.NotFound,
|
|
@@ -86,7 +87,7 @@ export function* askExecuteSmokeTest(
|
|
|
86
87
|
|
|
87
88
|
const finishedAt = yield* askDateNow();
|
|
88
89
|
const anyFailed = recorded.tests.some(
|
|
89
|
-
(t) => t.status
|
|
90
|
+
(t) => t.status === SmokeTestStatus.failed
|
|
90
91
|
);
|
|
91
92
|
|
|
92
93
|
return yield* askSetSmokeRunOutcome(
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
askDateNow,
|
|
3
|
+
askNewGuid,
|
|
4
|
+
askPlatformGetName,
|
|
5
|
+
askQueueSendMessages,
|
|
6
|
+
AskResponse,
|
|
7
|
+
QueueMessage,
|
|
8
|
+
} from 'quidproquo';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
SmokeRun,
|
|
12
|
+
SmokeRunStatus,
|
|
13
|
+
SmokeTestResult,
|
|
14
|
+
SmokeTestStatus,
|
|
15
|
+
} from '@qpqjs/test-models';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
SMOKE_RUN_QUEUE,
|
|
19
|
+
SMOKE_TEST_REQUESTED_MESSAGE_TYPE,
|
|
20
|
+
} from '../../constants/smokeRunQueue';
|
|
21
|
+
import { askSaveSmokeRun } from '../../data/askSaveSmokeRun';
|
|
22
|
+
import { SmokeTestRequestedPayload } from '../../models/SmokeTestRequestedQueueEvent';
|
|
23
|
+
import { SmokeTestDefinition } from '../../types/SmokeTestDefinition';
|
|
24
|
+
import { listSmokeTests } from './listSmokeTests';
|
|
25
|
+
|
|
26
|
+
const createInitialResult = (
|
|
27
|
+
test: SmokeTestDefinition,
|
|
28
|
+
index: number,
|
|
29
|
+
skip: boolean
|
|
30
|
+
): SmokeTestResult => ({
|
|
31
|
+
id: index + 1,
|
|
32
|
+
name: test.name,
|
|
33
|
+
status: skip ? SmokeTestStatus.skipped : SmokeTestStatus.pending,
|
|
34
|
+
message: skip ? 'deployed only' : '',
|
|
35
|
+
startedAt: null,
|
|
36
|
+
finishedAt: null,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const createTestRequestedMessage = (
|
|
40
|
+
runId: string,
|
|
41
|
+
test: SmokeTestDefinition
|
|
42
|
+
): QueueMessage<SmokeTestRequestedPayload> => ({
|
|
43
|
+
type: SMOKE_TEST_REQUESTED_MESSAGE_TYPE,
|
|
44
|
+
payload: { runId, testName: test.name },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Creates the run record with every registered test pending (or skipped, for
|
|
48
|
+
// a deployed-only test on the dev server), then sends one queue message per
|
|
49
|
+
// pending test so they execute in parallel. The record exists before the
|
|
50
|
+
// messages are sent so a poll (or a worker) that races the queue still finds
|
|
51
|
+
// it. A run of nothing but skips never gets a worker, so it is finished here.
|
|
52
|
+
export function* askStartSmokeRun(): AskResponse<SmokeRun> {
|
|
53
|
+
const runId = yield* askNewGuid();
|
|
54
|
+
const startedAt = yield* askDateNow();
|
|
55
|
+
const platformName = yield* askPlatformGetName();
|
|
56
|
+
|
|
57
|
+
const shouldSkip = (test: SmokeTestDefinition): boolean =>
|
|
58
|
+
!!test.deployedOnly && platformName === 'devServer';
|
|
59
|
+
|
|
60
|
+
const smokeTests = listSmokeTests();
|
|
61
|
+
const queued = smokeTests.filter((test) => !shouldSkip(test));
|
|
62
|
+
|
|
63
|
+
const smokeRun: SmokeRun = {
|
|
64
|
+
runId,
|
|
65
|
+
status:
|
|
66
|
+
queued.length === 0 ? SmokeRunStatus.passed : SmokeRunStatus.running,
|
|
67
|
+
startedAt,
|
|
68
|
+
finishedAt: queued.length === 0 ? startedAt : null,
|
|
69
|
+
tests: smokeTests.map((test, index) =>
|
|
70
|
+
createInitialResult(test, index, shouldSkip(test))
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
yield* askSaveSmokeRun(smokeRun);
|
|
75
|
+
|
|
76
|
+
const messages = queued.map((test) =>
|
|
77
|
+
createTestRequestedMessage(runId, test)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (messages.length > 0) {
|
|
81
|
+
yield* askQueueSendMessages(SMOKE_RUN_QUEUE, ...messages);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return smokeRun;
|
|
85
|
+
}
|
package/template/apps/qpqjs/services/test/service/src/smoke/harness/logic/smokeRun/listSmokeTests.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { smokeSuites } from '../../../suites/smokeSuites';
|
|
2
|
+
import { SmokeTestDefinition } from '../../types/SmokeTestDefinition';
|
|
3
|
+
|
|
4
|
+
/** Every registered test across all suites, in list order; a test's 1-based position is its run record id. */
|
|
5
|
+
export const listSmokeTests = (): SmokeTestDefinition[] =>
|
|
6
|
+
smokeSuites.flatMap((suite) => suite.tests);
|