Skip to content

Commit

Permalink
feat: Idempotency support (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickevansuk authored Aug 17, 2023
1 parent 0df6e81 commit ad0c68c
Show file tree
Hide file tree
Showing 26 changed files with 251 additions and 70 deletions.
4 changes: 2 additions & 2 deletions packages/openactive-integration-tests/test/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ The tests for these features cover all known edge cases, including both happy an
| notifications | Change of logistics notifications ([change-of-logistics-notifications](./notifications/change-of-logistics-notifications/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#change-of-logistics-notifications) | Notifications for when an opportunity's name, location, or start/end date/time are updated | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x12 |
| notifications | Customer notice notifications ([customer-notice-notifications](./notifications/customer-notice-notifications/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#customer-notice-notifications) | Text notifications broadcast to all registered attendees of an opportunity | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x4 |
| notifications | Opportunity attendance updates ([opportunity-attendance-updates](./notifications/opportunity-attendance-updates/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#opportunity-attendance-updates) | Allowing the broker to recieve updates for when an attendee attends an event | [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x8 |
| payment | Free opportunities ([free-opportunities](./payment/free-opportunities/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#free-opportunities) | The most simple form of booking, for free opportunities. Does not check for leases. | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x10 |
| payment | Opportunities with a non-zero price ([non-free-opportunities](./payment/non-free-opportunities/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#step-by-step-process-description) | The most simple form of booking with payment. Does not check for leases. | [TestOpportunityBookableNonFree](https://openactive.io/test-interface#TestOpportunityBookableNonFree) x6, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x2 |
| payment | Free opportunities ([free-opportunities](./payment/free-opportunities/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#free-opportunities) | The most simple form of booking, for free opportunities. Does not check for leases. | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x14 |
| payment | Opportunities with a non-zero price ([non-free-opportunities](./payment/non-free-opportunities/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#step-by-step-process-description) | The most simple form of booking with payment. Does not check for leases. | [TestOpportunityBookableNonFree](https://openactive.io/test-interface#TestOpportunityBookableNonFree) x9, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x3 |
| payment | Payment reconciliation detail validation ([payment-reconciliation-detail-validation](./payment/payment-reconciliation-detail-validation/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#payment-reconciliation-detail-validation) | Booking with valid, invalid, and missing Payment details | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x12, [TestOpportunityBookableUsingPayment](https://openactive.io/test-interface#TestOpportunityBookableUsingPayment) x15, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x5 |
| payment | prepayment optional ([prepayment-optional](./payment/prepayment-optional/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#booking-without-payment) | Support for booking with optional payment | [TestOpportunityBookableNonFreePrepaymentOptional](https://openactive.io/test-interface#TestOpportunityBookableNonFreePrepaymentOptional) x8 |
| payment | prepayment required ([prepayment-required](./payment/prepayment-required/README.md)) | Optional<br>[View Spec](https://www.openactive.io/open-booking-api/EditorsDraft/#booking-without-payment) | Support for booking with required payment | [TestOpportunityBookableNonFreePrepaymentRequired](https://openactive.io/test-interface#TestOpportunityBookableNonFreePrepaymentRequired) x8 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ FeatureHelper.describeFeature(module, {
opportunityFeedExtractResponses: c2.getStage('assertOpportunityCapacityAfterC2').getOutput().opportunityFeedExtractResponses,
orderItems: fetchOpportunities.getOutput().orderItems,
}),
paymentIdentifierIfPaid: FlowStageRecipes.createRandomPaymentIdentifierIfPaid(),
});

// ## Set up tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ FeatureHelper.describeFeature(module, {
},
(configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
const { fetchOpportunities, c1, c2, defaultFlowStageParams } = FlowStageRecipes.initialiseSimpleC1C2Flow(orderItemCriteriaList, logger);
const paymentIdentifierIfPaid = FlowStageRecipes.createRandomPaymentIdentifierIfPaid();
const p = new PFlowStage({
...defaultFlowStageParams,
prerequisite: c2.getLastStage(),
Expand All @@ -48,6 +49,7 @@ FeatureHelper.describeFeature(module, {
totalPaymentDue: c2.getStage('c2').getOutput().totalPaymentDue,
prepayment: c2.getStage('c2').getOutput().prepayment,
}),
paymentIdentifierIfPaid,
});
const [customerRejection, orderFeedUpdate] = OrderFeedUpdateFlowStageUtils.wrap({
wrappedStageFn: prerequisite => (new CustomerRejectOrderProposalFlowStage({
Expand All @@ -73,6 +75,7 @@ FeatureHelper.describeFeature(module, {
orderProposalVersion: orderFeedUpdate.getOutput().orderProposalVersion,
positionOrderIntakeFormMap: c1.getStage('c1').getOutput().positionOrderIntakeFormMap,
}),
paymentIdentifierIfPaid,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ FeatureHelper.describeFeature(module, {
},
(configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
const { fetchOpportunities, c1, c2, defaultFlowStageParams } = FlowStageRecipes.initialiseSimpleC1C2Flow(orderItemCriteriaList, logger);
const paymentIdentifierIfPaid = FlowStageRecipes.createRandomPaymentIdentifierIfPaid();
const p = new PFlowStage({
...defaultFlowStageParams,
prerequisite: c2.getLastStage(),
Expand All @@ -42,6 +43,7 @@ FeatureHelper.describeFeature(module, {
totalPaymentDue: c2.getStage('c2').getOutput().totalPaymentDue,
prepayment: c2.getStage('c2').getOutput().prepayment,
}),
paymentIdentifierIfPaid,
});
const b = FlowStageRecipes.runs.book.simpleBAssertCapacity(p, defaultFlowStageParams, {
isExpectedToSucceed: false,
Expand All @@ -55,6 +57,7 @@ FeatureHelper.describeFeature(module, {
orderProposalVersion: p.getOutput().orderProposalVersion,
positionOrderIntakeFormMap: c1.getStage('c1').getOutput().positionOrderIntakeFormMap,
}),
paymentIdentifierIfPaid,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ FeatureHelper.describeFeature(module, {
},
(configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
const { fetchOpportunities, c1, c2, defaultFlowStageParams } = FlowStageRecipes.initialiseSimpleC1C2Flow(orderItemCriteriaList, logger);
const paymentIdentifierIfPaid = FlowStageRecipes.createRandomPaymentIdentifierIfPaid();
const p = new PFlowStage({
...defaultFlowStageParams,
prerequisite: c2.getLastStage(),
Expand All @@ -48,6 +49,7 @@ FeatureHelper.describeFeature(module, {
totalPaymentDue: c2.getStage('c2').getOutput().totalPaymentDue,
prepayment: c2.getStage('c2').getOutput().prepayment,
}),
paymentIdentifierIfPaid,
});
const [simulateSellerRejection, orderFeedUpdate] = OrderFeedUpdateFlowStageUtils.wrap({
wrappedStageFn: prerequisite => (new TestInterfaceActionFlowStage({
Expand Down Expand Up @@ -79,6 +81,7 @@ FeatureHelper.describeFeature(module, {
orderProposalVersion: orderFeedUpdate.getOutput().orderProposalVersion,
positionOrderIntakeFormMap: c1.getStage('c1').getOutput().positionOrderIntakeFormMap,
}),
paymentIdentifierIfPaid,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ FeatureHelper.describeFeature(module, {
(configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
// ## Initiate Flow Stages
const { fetchOpportunities, c1, c2, defaultFlowStageParams } = FlowStageRecipes.initialiseSimpleC1C2Flow(orderItemCriteriaList, logger);
const paymentIdentifierIfPaid = FlowStageRecipes.createRandomPaymentIdentifierIfPaid();
const p = new PFlowStage({
...defaultFlowStageParams,
prerequisite: c2.getLastStage(),
Expand All @@ -49,6 +50,7 @@ FeatureHelper.describeFeature(module, {
totalPaymentDue: c2.getStage('c2').getOutput().totalPaymentDue,
prepayment: c2.getStage('c2').getOutput().prepayment,
}),
paymentIdentifierIfPaid,
});
const [simulateSellerAmendment, sellerAmendmentOrderFeedUpdate] = OrderFeedUpdateFlowStageUtils.wrap({
// FlowStage that is getting wrapped
Expand Down Expand Up @@ -102,6 +104,7 @@ FeatureHelper.describeFeature(module, {
orderProposalVersion: p.getOutput().orderProposalVersion,
prepayment: p.getOutput().prepayment,
}),
paymentIdentifierIfPaid,
},
});
// Using the new proposal version should fail
Expand All @@ -116,6 +119,7 @@ FeatureHelper.describeFeature(module, {
orderProposalVersion: sellerAmendmentOrderFeedUpdate.getOutput().orderProposalVersion,
prepayment: sellerAmendmentOrderFeedUpdate.getOutput().prepayment,
}),
paymentIdentifierIfPaid,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ FeatureHelper.describeFeature(module, {
orderItems: secondAttemptFetchOpportunities.getOutput().orderItems,
opportunityFeedExtractResponses: secondAttemptC2.getStage('assertOpportunityCapacityAfterC2').getOutput().opportunityFeedExtractResponses,
}),
paymentIdentifierIfPaid: FlowStageRecipes.createRandomPaymentIdentifierIfPaid(),
});

// # Set up Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ FeatureHelper.describeFeature(module, {
orderItems: secondAttemptFetchOpportunities.getOutput().orderItems,
opportunityFeedExtractResponses: secondAttemptC2.getStage('assertOpportunityCapacityAfterC2').getOutput().opportunityFeedExtractResponses,
}),
paymentIdentifierIfPaid: FlowStageRecipes.createRandomPaymentIdentifierIfPaid(),
});

// # Set up Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ FeatureHelper.describeFeature(module, {
orderItems: secondAttemptFetchOpportunities.getOutput().orderItems,
opportunityFeedExtractResponses: secondAttemptC2.getStage('assertOpportunityCapacityAfterC2').getOutput().opportunityFeedExtractResponses,
}),
paymentIdentifierIfPaid: FlowStageRecipes.createRandomPaymentIdentifierIfPaid(),
});

// # Set up Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ FeatureHelper.describeFeature(module, {
orderItems: secondAttemptFetchOpportunities.getOutput().orderItems,
opportunityFeedExtractResponses: secondAttemptC2.getStage('assertOpportunityCapacityAfterC2').getOutput().opportunityFeedExtractResponses,
}),
paymentIdentifierIfPaid: FlowStageRecipes.createRandomPaymentIdentifierIfPaid(),
});

// # Set up Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@
},
"free-opportunities": {
"primary": {
"TestOpportunityBookableFree": 10
"TestOpportunityBookableFree": 14
}
},
"non-free-opportunities": {
"primary": {
"TestOpportunityBookableNonFree": 6,
"TestOpportunityBookable": 2
"TestOpportunityBookableNonFree": 9,
"TestOpportunityBookable": 3
}
},
"payment-reconciliation-detail-validation": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ See also: [.NET Tutorial](https://tutorials.openactive.io/open-booking-sdk/quick
### Test prerequisites
Opportunities that match the following criteria must exist in the booking system (for each configured `bookableOpportunityTypesInScope`) for the configured primary Seller in order to use `useRandomOpportunities: true`. Alternatively the following `testOpportunityCriteria` values must be supported by the [test interface](https://openactive.io/test-interface/) of the booking system for `useRandomOpportunities: false`.

[TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x10
[TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x14


### Running tests for only this feature
Expand All @@ -37,6 +37,7 @@ Update `default.json` within `packages/openactive-integration-tests/config/` as

| Identifier | Name | Description | Prerequisites per Opportunity Type |
|------------|------|-------------|---------------|
| [opportunity-free-idempotency](./implemented/opportunity-free-idempotency-test.js) | Successful booking of free opportunity with idempotency | Testing idempotency of the B call for free opportunities | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x4 |
| [opportunity-free-must-not-include-prepayment](./implemented/opportunity-free-must-not-include-prepayment-test.js) | Free opportunities must have either a `openBookingPrepayment` value of Unspecified, or have no `openBookingPrepayment` specified | Assert that no opportunities that match criteria 'TestOpportunityBookableFreePrepaymentOptional' or 'TestOpportunityBookableFreePrepaymentRequired' are available in the opportunity feeds. | |
| [opportunity-free](./implemented/opportunity-free-test.js) | Successful booking without payment property | A successful end to end booking without the `payment` property included. | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x2 |
| [opportunity-free-unnecessary-payment-error](./implemented/opportunity-free-unnecessary-payment-error-test.js) | Fail free bookings which include erroneous payment property | C1, C2 and B with payment property: payment property is provided but not expected in the request, so an UnnecessaryPaymentDetailsError must be returned. | [TestOpportunityBookableFree](https://openactive.io/test-interface#TestOpportunityBookableFree) x2 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { omit } = require('lodash');
const { FeatureHelper } = require('../../../../helpers/feature-helper');
const { FlowStageRecipes, FlowStageUtils } = require('../../../../helpers/flow-stages');

FeatureHelper.describeFeature(module, {
testCategory: 'payment',
testFeature: 'free-opportunities',
testFeatureImplemented: true,
testIdentifier: 'opportunity-free-idempotency',
testName: 'Successful booking of free opportunity with idempotency',
testDescription: 'Testing idempotency of the B call for free opportunities',
testOpportunityCriteria: 'TestOpportunityBookableFree',
// This must also be TestOpportunityBookableFree as the entire Order must be free.
controlOpportunityCriteria: 'TestOpportunityBookableFree',
}, (configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
const {
fetchOpportunities,
bookRecipe,
defaultFlowStageParams,
bookRecipeArgs,
} = FlowStageRecipes.initialiseSimpleC1C2BookFlow(orderItemCriteriaList, logger);
const idempotentRepeatB = FlowStageRecipes.idempotentRepeatBAfterBook(
orderItemCriteriaList,
bookRecipe,
defaultFlowStageParams,
omit(bookRecipeArgs, ['prerequisite']),
);
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(fetchOpportunities);
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(bookRecipe);
describe('idempotent repeat B', () => {
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(idempotentRepeatB);
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { omit } = require('lodash');
const { FeatureHelper } = require('../../../../helpers/feature-helper');
const { FlowStageRecipes, FlowStageUtils } = require('../../../../helpers/flow-stages');

Expand All @@ -12,18 +13,22 @@ FeatureHelper.describeFeature(module, {
testOpportunityCriteria: 'TestOpportunityBookableFree',
// This must also be TestOpportunityBookableFree as the entire Order must be free.
controlOpportunityCriteria: 'TestOpportunityBookableFree',
}, (configuration, orderItemCriteriaList, featureIsImplemented, logger, opportunityType, bookingFlow) => {
const { fetchOpportunities, bookRecipe, defaultFlowStageParams, bookRecipeGetFirstStageInput, bookRecipeGetAssertOpportunityCapacityInput } = FlowStageRecipes.initialiseSimpleBookOnlyFlow(orderItemCriteriaList, logger);
const idempotentRepeatB = FlowStageRecipes.idempotentRepeatBAfterBook(orderItemCriteriaList, bookRecipe, defaultFlowStageParams, {
getFirstStageInput: bookRecipeGetFirstStageInput,
getAssertOpportunityCapacityInput: bookRecipeGetAssertOpportunityCapacityInput,
});
}, (configuration, orderItemCriteriaList, featureIsImplemented, logger) => {
const {
fetchOpportunities,
bookRecipe,
defaultFlowStageParams,
bookRecipeArgs,
} = FlowStageRecipes.initialiseSimpleBookOnlyFlow(orderItemCriteriaList, logger);
const idempotentRepeatB = FlowStageRecipes.idempotentRepeatBAfterBook(
orderItemCriteriaList,
bookRecipe,
defaultFlowStageParams,
omit(bookRecipeArgs, ['prerequisite']),
);
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(fetchOpportunities);
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(bookRecipe);
// Remove this condition once https://github.com/openactive/OpenActive.Server.NET/issues/100 is fixed.
if (bookingFlow === 'OpenBookingApprovalFlow') {
describe('idempotent repeat B', () => {
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(idempotentRepeatB);
});
}
describe('idempotent repeat B', () => {
FlowStageUtils.describeRunAndCheckIsSuccessfulAndValid(idempotentRepeatB);
});
});
Loading

0 comments on commit ad0c68c

Please sign in to comment.