From 589099d2ff796ee1ce3985b1c385591d321b4a3c Mon Sep 17 00:00:00 2001 From: Luke Winship Date: Mon, 11 Mar 2024 12:14:26 +0000 Subject: [PATCH] feat: Add single-seller feature, which is mutually exclusive with multiple-sellers (#640) --- ...mple.single-seller-client-credentials.json | 1 + config/.example.single-seller.json | 1 + config/default.json | 1 + .../documentation/generator.js | 16 ++++-- .../test/features/README.md | 1 + .../test/features/categories.json | 1 + .../features/core/multiple-sellers/README.md | 17 ++++++ .../single-seller-implemented-test.js | 13 +++++ .../features/core/single-seller/README.md | 54 +++++++++++++++++++ .../features/core/single-seller/feature.json | 10 ++++ .../only-primary-seller-configured-test.js | 21 ++++++++ .../multiple-sellers-implemented-test.js | 13 +++++ .../test/features/criteria-requirements.json | 1 + .../test/features/tests-implemented.json | 6 ++- .../test/global.d.ts | 2 +- .../test/helpers/feature-helper.js | 42 ++++++++++++++- 16 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 packages/openactive-integration-tests/test/features/core/multiple-sellers/not-implemented/single-seller-implemented-test.js create mode 100644 packages/openactive-integration-tests/test/features/core/single-seller/README.md create mode 100644 packages/openactive-integration-tests/test/features/core/single-seller/feature.json create mode 100644 packages/openactive-integration-tests/test/features/core/single-seller/implemented/only-primary-seller-configured-test.js create mode 100644 packages/openactive-integration-tests/test/features/core/single-seller/not-implemented/multiple-sellers-implemented-test.js diff --git a/config/.example.single-seller-client-credentials.json b/config/.example.single-seller-client-credentials.json index 048e8efd0c..111d45e2fe 100644 --- a/config/.example.single-seller-client-credentials.json +++ b/config/.example.single-seller-client-credentials.json @@ -24,6 +24,7 @@ "integrationTests": { "implementedFeatures": { "multiple-sellers": false, + "single-seller": true, "booking-partner-authentication": false, "dynamic-client-registration": false, "business-to-consumer-tax-calculation-net": false, diff --git a/config/.example.single-seller.json b/config/.example.single-seller.json index a57865c82c..6c287fcb5a 100644 --- a/config/.example.single-seller.json +++ b/config/.example.single-seller.json @@ -22,6 +22,7 @@ "integrationTests": { "implementedFeatures": { "multiple-sellers": false, + "single-seller": true, "booking-partner-authentication": false, "dynamic-client-registration": false, "business-to-consumer-tax-calculation-net": false, diff --git a/config/default.json b/config/default.json index a8db0738bf..514120bf6a 100644 --- a/config/default.json +++ b/config/default.json @@ -72,6 +72,7 @@ "prepayment-unavailable": true, "minimal-proposal": true, "proposal-amendment": true, + "single-seller": false, "multiple-sellers": true, "payment-reconciliation-detail-validation": true, "booking-window": true, diff --git a/packages/openactive-integration-tests/documentation/generator.js b/packages/openactive-integration-tests/documentation/generator.js index 0aa15d6b8b..05aa28d2f2 100644 --- a/packages/openactive-integration-tests/documentation/generator.js +++ b/packages/openactive-integration-tests/documentation/generator.js @@ -59,6 +59,17 @@ const INDEX_TESTS_IMPLEMENTED_JSON_FILE = path.join(FEATURES_ROOT, 'tests-implem * }} FeatureMetadataItem */ +const FEATURES_NOT_REQUIRED_FOR_DEFAULT_JSON = new Set([ + /* Not required because the default behaviour allows for both opportunities + which can and can not be cancelled */ + 'customer-requested-cancellation-always-allowed', + /* change-of-logistics-notification is also temporarily disabled while the + Reference Implementation catches up to its new specification. */ + 'change-of-logistics-notifications', + // This is not required because it is mutually exclusive with multiple-sellers + 'single-seller', +]); + const rootDirectory = path.join(__dirname, '../'); // Stub global config @@ -80,9 +91,8 @@ const testMetadata = fg.sync(jestConfig.testMatch, { cwd: rootDirectory }).map(f // ## Validate the test metadata const expectedPath = `test/features/${renderFullTestPath(data)}`; chai.expect(expectedPath, `Expected ${file} to contain metadata matching its path`).to.equal(file); - // All features in default.json should be true except for customer-requested-cancellation-always-allowed - // change-of-logistics-notification is also temporarily disabled while the Reference Implementation catches up to its new specification. - const expectedValue = data.testFeature !== 'customer-requested-cancellation-always-allowed' && data.testFeature !== 'change-of-logistics-notifications'; + // All features in default.json should be true, with some exceptions + const expectedValue = !FEATURES_NOT_REQUIRED_FOR_DEFAULT_JSON.has(data.testFeature); chai.expect(defaultConfig.integrationTests.implementedFeatures, `Expected default.json to contain feature '${data.testFeature} set to "${expectedValue}"'`).to.have.property(data.testFeature).to.equal(expectedValue); return data; }); diff --git a/packages/openactive-integration-tests/test/features/README.md b/packages/openactive-integration-tests/test/features/README.md index 113f0dd3e6..2c517a37f2 100644 --- a/packages/openactive-integration-tests/test/features/README.md +++ b/packages/openactive-integration-tests/test/features/README.md @@ -37,6 +37,7 @@ The tests for these features cover all known edge cases, including both happy an | cancellation | cancellationMessage for Seller Requested Cancellation ([seller-requested-cancellation-message](./cancellation/seller-requested-cancellation-message/README.md)) | Optional
[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#seller-requested-cancellation) | A message associated with a Cancellation triggered by the Seller through the Booking System | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x4 | | cancellation | Seller Requested Replacement ([seller-requested-replacement](./cancellation/seller-requested-replacement/README.md)) | Optional
[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#cancellation-replacement-refund-calculation-and-notification) | Replacement triggered by the Seller through the Booking System | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x4 | | core | Multiple Sellers ([multiple-sellers](./core/multiple-sellers/README.md)) | Optional
[View Spec](https://openactive.io/open-booking-api/EditorsDraft/#booking-pre-conditions) | The booking system is multi-tenanted and provides services to multiple sellers. | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x6 | +| core | Single Seller ([single-seller](./core/single-seller/README.md)) | Optional
[View Spec](https://openactive.io/open-booking-api/EditorsDraft/#booking-pre-conditions) | The booking system only supports providing services to one seller. | | | core | Test interface ([test-interface](./core/test-interface/README.md)) | Optional
[View Spec](https://openactive.io/test-interface/) | Open Booking API Test Interface implementation | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x1 | | details-capture | Additional Details capture ([additional-details-capture](./details-capture/additional-details-capture/README.md)) | Optional
[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#additional-details-capture) | Support for capturing additional details with required set to true | [TestOpportunityBookableAdditionalDetails](https://openactive.io/test-interface#TestOpportunityBookableAdditionalDetails) x9, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x3 | | details-capture | Simple Book including Attendee Details capture ([attendee-details-capture](./details-capture/attendee-details-capture/README.md)) | Optional
[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#attendee-details-capture) | Support for capturing attendee details | [TestOpportunityBookableAttendeeDetails](https://openactive.io/test-interface#TestOpportunityBookableAttendeeDetails) x6, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x2 | diff --git a/packages/openactive-integration-tests/test/features/categories.json b/packages/openactive-integration-tests/test/features/categories.json index 1bd49f3279..b91bf324b5 100644 --- a/packages/openactive-integration-tests/test/features/categories.json +++ b/packages/openactive-integration-tests/test/features/categories.json @@ -10,6 +10,7 @@ "order-deletion": true, "multiple-sellers": true, "opportunity-feed": true, + "single-seller": true, "test-interface": true }, "access": { diff --git a/packages/openactive-integration-tests/test/features/core/multiple-sellers/README.md b/packages/openactive-integration-tests/test/features/core/multiple-sellers/README.md index 36bcd01bc6..ded7825cb3 100644 --- a/packages/openactive-integration-tests/test/features/core/multiple-sellers/README.md +++ b/packages/openactive-integration-tests/test/features/core/multiple-sellers/README.md @@ -40,3 +40,20 @@ Update `default.json` within `packages/openactive-integration-tests/config/` as | [seller-access-restricted-by-auth](./implemented/seller-access-restricted-by-auth-test.js) | Credentials for Seller (a) must not provide access to make bookings for Seller (b) | Using primary seller auth, make a call to C1, C2, and P/B for the secondary seller, expecting all calls to fail with InvalidAuthorizationDetailsError | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x4 | + +## 'Not Implemented' tests + + +Update `default.json` within `packages/openactive-integration-tests/config/` as follows to enable 'Not Implemented' testing for this feature: + +```json +"implementedFeatures": { + ... + "multiple-sellers": false, + ... +} +``` + +| Identifier | Name | Description | Prerequisites per Opportunity Type | +|------------|------|-------------|---------------| +| [single-seller-implemented](./not-implemented/single-seller-implemented-test.js) | Single Seller feature must be implemented if Multiple Sellers is not implemented | Either one, and only one, of the Multiple Sellers feature and the Single Seller feature must be implemented | | diff --git a/packages/openactive-integration-tests/test/features/core/multiple-sellers/not-implemented/single-seller-implemented-test.js b/packages/openactive-integration-tests/test/features/core/multiple-sellers/not-implemented/single-seller-implemented-test.js new file mode 100644 index 0000000000..2bf23c0d63 --- /dev/null +++ b/packages/openactive-integration-tests/test/features/core/multiple-sellers/not-implemented/single-seller-implemented-test.js @@ -0,0 +1,13 @@ +const { FeatureHelper } = require('../../../../helpers/feature-helper'); + +FeatureHelper.describeFeatureShouldBeImplementedIfOtherFeaturesAreNot(module, { + testCategory: 'core', + testFeature: 'multiple-sellers', + testFeatureImplemented: false, + testIdentifier: 'single-seller-implemented', + testName: 'Single Seller feature must be implemented if Multiple Sellers is not implemented', + testDescription: 'Either one, and only one, of the Multiple Sellers feature and the Single Seller feature must be implemented', + otherFeaturesWhichAreMutuallyExclusiveWithThisOne: [ + 'single-seller', + ], +}); diff --git a/packages/openactive-integration-tests/test/features/core/single-seller/README.md b/packages/openactive-integration-tests/test/features/core/single-seller/README.md new file mode 100644 index 0000000000..765c7dd98c --- /dev/null +++ b/packages/openactive-integration-tests/test/features/core/single-seller/README.md @@ -0,0 +1,54 @@ +[< Return to Overview](../../README.md) +# Single Seller (single-seller) + +The booking system only supports providing services to one seller. + + +https://openactive.io/open-booking-api/EditorsDraft/#booking-pre-conditions + +Coverage Status: **complete** + + + +### Running tests for only this feature + +```bash +npm start -- --runInBand test/features/core/single-seller/ +``` + + + +## 'Implemented' tests + +Update `default.json` within `packages/openactive-integration-tests/config/` as follows to enable 'Implemented' testing for this feature: + +```json +"implementedFeatures": { + ... + "single-seller": true, + ... +} +``` + +| Identifier | Name | Description | Prerequisites per Opportunity Type | +|------------|------|-------------|---------------| +| [only-primary-seller-configured](./implemented/only-primary-seller-configured-test.js) | Only the primary seller should be configured | If the single-seller feature is implemented, multiple-sellers is not enabled, and so a secondary seller should not be configured. | | + + + +## 'Not Implemented' tests + + +Update `default.json` within `packages/openactive-integration-tests/config/` as follows to enable 'Not Implemented' testing for this feature: + +```json +"implementedFeatures": { + ... + "single-seller": false, + ... +} +``` + +| Identifier | Name | Description | Prerequisites per Opportunity Type | +|------------|------|-------------|---------------| +| [multiple-sellers-implemented](./not-implemented/multiple-sellers-implemented-test.js) | Multiple Sellers feature must be implemented if Single Seller is not implemented | Either one, and only one, of the Multiple Sellers feature and Single Seller feature must be implemented | | diff --git a/packages/openactive-integration-tests/test/features/core/single-seller/feature.json b/packages/openactive-integration-tests/test/features/core/single-seller/feature.json new file mode 100644 index 0000000000..4965419fab --- /dev/null +++ b/packages/openactive-integration-tests/test/features/core/single-seller/feature.json @@ -0,0 +1,10 @@ +{ + "category": "core", + "identifier": "single-seller", + "name": "Single Seller", + "description": "The booking system only supports providing services to one seller.", + "explainer": "", + "specificationReference": "https://openactive.io/open-booking-api/EditorsDraft/#booking-pre-conditions", + "required": false, + "coverageStatus": "complete" +} \ No newline at end of file diff --git a/packages/openactive-integration-tests/test/features/core/single-seller/implemented/only-primary-seller-configured-test.js b/packages/openactive-integration-tests/test/features/core/single-seller/implemented/only-primary-seller-configured-test.js new file mode 100644 index 0000000000..c66f6b702e --- /dev/null +++ b/packages/openactive-integration-tests/test/features/core/single-seller/implemented/only-primary-seller-configured-test.js @@ -0,0 +1,21 @@ +const { expect } = require('chai'); +const { FeatureHelper } = require('../../../../helpers/feature-helper'); + +const { SELLER_CONFIG } = global; + +FeatureHelper.describeFeature(module, { + testCategory: 'core', + testFeature: 'single-seller', + testFeatureImplemented: true, + testIdentifier: 'only-primary-seller-configured', + testName: 'Only the primary seller should be configured', + testDescription: 'If the single-seller feature is implemented, multiple-sellers is not enabled, and so a secondary seller should not be configured.', + doesNotUseOpportunitiesMode: true, +}, () => { + describe('Feature', () => { + it('should only be implemented if there is only one `primary` seller configured', () => { + expect(SELLER_CONFIG).to.have.nested.property('primary.@id'); + expect(SELLER_CONFIG).to.not.have.nested.property('secondary.@id'); + }); + }); +}); diff --git a/packages/openactive-integration-tests/test/features/core/single-seller/not-implemented/multiple-sellers-implemented-test.js b/packages/openactive-integration-tests/test/features/core/single-seller/not-implemented/multiple-sellers-implemented-test.js new file mode 100644 index 0000000000..ad1cab3e77 --- /dev/null +++ b/packages/openactive-integration-tests/test/features/core/single-seller/not-implemented/multiple-sellers-implemented-test.js @@ -0,0 +1,13 @@ +const { FeatureHelper } = require('../../../../helpers/feature-helper'); + +FeatureHelper.describeFeatureShouldBeImplementedIfOtherFeaturesAreNot(module, { + testCategory: 'core', + testFeature: 'single-seller', + testFeatureImplemented: false, + testIdentifier: 'multiple-sellers-implemented', + testName: 'Multiple Sellers feature must be implemented if Single Seller is not implemented', + testDescription: 'Either one, and only one, of the Multiple Sellers feature and Single Seller feature must be implemented', + otherFeaturesWhichAreMutuallyExclusiveWithThisOne: [ + 'multiple-sellers', + ], +}); diff --git a/packages/openactive-integration-tests/test/features/criteria-requirements.json b/packages/openactive-integration-tests/test/features/criteria-requirements.json index dbd7fb0534..694baf7883 100644 --- a/packages/openactive-integration-tests/test/features/criteria-requirements.json +++ b/packages/openactive-integration-tests/test/features/criteria-requirements.json @@ -136,6 +136,7 @@ } }, "opportunity-feed": {}, + "single-seller": {}, "test-interface": { "primary": { "TestOpportunityBookable": 1 diff --git a/packages/openactive-integration-tests/test/features/tests-implemented.json b/packages/openactive-integration-tests/test/features/tests-implemented.json index fb06a6be46..e976c6929f 100644 --- a/packages/openactive-integration-tests/test/features/tests-implemented.json +++ b/packages/openactive-integration-tests/test/features/tests-implemented.json @@ -115,12 +115,16 @@ }, "multiple-sellers": { "implementedTestFiles": 2, - "notImplementedTestFiles": 0 + "notImplementedTestFiles": 1 }, "opportunity-feed": { "implementedTestFiles": 0, "notImplementedTestFiles": 0 }, + "single-seller": { + "implementedTestFiles": 1, + "notImplementedTestFiles": 1 + }, "test-interface": { "implementedTestFiles": 1, "notImplementedTestFiles": 0 diff --git a/packages/openactive-integration-tests/test/global.d.ts b/packages/openactive-integration-tests/test/global.d.ts index c158245cc9..f349fe044d 100644 --- a/packages/openactive-integration-tests/test/global.d.ts +++ b/packages/openactive-integration-tests/test/global.d.ts @@ -35,7 +35,7 @@ declare global { }; SELLER_CONFIG: { primary: SellerConfig; - secondary: SellerConfig + secondary?: SellerConfig }; AUTHENTICATION_FAILURE: boolean; DYNAMIC_REGISTRATION_FAILURE: boolean; diff --git a/packages/openactive-integration-tests/test/helpers/feature-helper.js b/packages/openactive-integration-tests/test/helpers/feature-helper.js index 91d23917e4..4dad624810 100644 --- a/packages/openactive-integration-tests/test/helpers/feature-helper.js +++ b/packages/openactive-integration-tests/test/helpers/feature-helper.js @@ -277,9 +277,12 @@ class FeatureHelper { * set of features are. * * @param {NodeModule} documentationModule - * @param {Omit & { + * @param {Omit & { * otherFeaturesWhichImplyThisOne: string[]; * }} configuration + * - `otherFeaturesWhichImplyThisOne` is an array of feature names. If any + * of these features are implemented, then the feature in focus MUST also + * be implemented */ static describeFeatureShouldBeImplementedIfOtherFeaturesAre(documentationModule, configuration) { const otherFeaturesSummary = configuration.otherFeaturesWhichImplyThisOne.map(f => `'${f}'`).join(' and '); @@ -299,6 +302,43 @@ class FeatureHelper { }); } + /** + * Use this for a `not-implemented` test for a feature that should be + * implemented if another given set of features are NOT. i.e. this feature + * is mutually exclusive with another set of features. + * + * @param {NodeModule} documentationModule + * @param {Omit & { + * otherFeaturesWhichAreMutuallyExclusiveWithThisOne: string[]; + * }} configuration + * - `otherFeaturesWhichAreMutuallyExclusiveWithThisOne` is an array of + * feature names. If all of these features are not implemented, then + * the feature in focus MUST be implemented. + */ + static describeFeatureShouldBeImplementedIfOtherFeaturesAreNot(documentationModule, configuration) { + const otherFeaturesSummary = configuration.otherFeaturesWhichAreMutuallyExclusiveWithThisOne + .map(f => `'${f}'`) + .join(' and '); + this.describeFeature(documentationModule, { + testDescription: `This feature must be implemented if features: ${otherFeaturesSummary} are NOT implemented`, + skipMultiple: true, + doesNotUseOpportunitiesMode: true, + ...configuration, + }, () => { + describe('Feature', () => { + it(`must be implemented if other features: ${otherFeaturesSummary} are NOT`, () => { + expect(IMPLEMENTED_FEATURES).to.include( + Object.fromEntries( + configuration.otherFeaturesWhichAreMutuallyExclusiveWithThisOne.map( + f => [f, true], + ), + ), + ); + }); + }); + }); + } + /** * Use this for a `not-implemented` test for a feature that should be implemented if a specific flow is implemented. *