Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Idempotency support #552

Merged
merged 6 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']),
Copy link
Contributor

@lukehesluke lukehesluke Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue was this: Idempotent B wasn't getting the same starting args as B, which was messing up its expectations for what capacity should be asserted for after the 2nd B. This fixes that

);
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