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

feature(opportunity-outside-range-c1-c2) #244

Merged
merged 5 commits into from Oct 21, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/launch.json
Expand Up @@ -5,14 +5,14 @@
"version": "0.2.0",
"configurations": [
{
"name": "integration-tests - test/features/leasing/named-leasing/implemented/lease-opportunity-capacity-update-test.js",
"name": "integration-tests - test/features/leasing/named-leasing/implemented/lease-response-test.js",
"request": "launch",
"runtimeArgs": [
"run-script",
"start-tests",
"--",
"--runInBand",
"test/features/leasing/named-leasing/implemented/lease-opportunity-capacity-update-test.js"
"test/features/leasing/named-leasing/implemented/lease-response-test.js"
],
"runtimeExecutable": "npm",
"skipFiles": [
Expand Down
Expand Up @@ -70,7 +70,7 @@ The tests for these features are fully stubbed, and are not yet implemented.
| notifications | Opportunity attendance updates ([opportunity-attendance-updates](./notifications/opportunity-attendance-updates/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#opportunity-attendance-updates) | Allowing the broker to recieve updates for when an attendee attends an event | |
| payment | Payment reconciliation detail validation ([payment-reconciliation-detail-validation](./payment/payment-reconciliation-detail-validation/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#payment-reconciliation-detail-validation) | Booking with valid, invalid, and missing Payment details | |
| restrictions | Booking restrictions ([booking-restrictions](./restrictions/booking-restrictions/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#booking-restrictions) | Support for genderRestriction, ageRestriction and additionalAdmissionRestriction | |
| restrictions | validFromBeforeStartDate booking window ([booking-window](./restrictions/booking-window/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#definition-of-a-bookable-opportunity-and-offer-pair) | Duration of window before an opportunity where it is bookable | [TestOpportunityBookableWithinValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableWithinValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x1 |
| restrictions | validFromBeforeStartDate booking window ([booking-window](./restrictions/booking-window/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#definition-of-a-bookable-opportunity-and-offer-pair) | Duration of window before an opportunity where it is bookable | [TestOpportunityBookableWithinValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableWithinValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x2, [TestOpportunityBookableOutsideValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableOutsideValidFromBeforeStartDate) x3 |
| tax | Business-to-business Tax Calculation (TaxGross) ([business-to-business-tax-calculation-gross](./tax/business-to-business-tax-calculation-gross/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#business-to-business-tax-calculation-by-booking-system-is-optional) | Tax calculation when the customer is of type Organization (business-to-business), when the seller has taxMode TaxGross | |
| tax | Business-to-business Tax Calculation (TaxNet) ([business-to-business-tax-calculation-net](./tax/business-to-business-tax-calculation-net/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#business-to-business-tax-calculation-by-booking-system-is-optional) | Tax calculation when the customer is of type Organization (business-to-business), when the seller has taxMode TaxNet | |
| tax | Business-to-consumer Tax Calculation (TaxGross) ([business-to-consumer-tax-calculation-gross](./tax/business-to-consumer-tax-calculation-gross/README.md)) | [Optional](https://www.openactive.io/open-booking-api/EditorsDraft/#business-to-consumer-tax-calculation-by-booking-system-is-mandatory) | Tax calculation when the customer is of type Person (business-to-consumer), when the seller has taxMode TaxGross | |
Expand Down
Expand Up @@ -121,6 +121,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should not take into account leased opportunities on this order
itShouldHaveCapacity(5, c1, () => state.c1Response);
});

Expand All @@ -132,6 +133,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should not take into account leased opportunities on this order
itShouldHaveCapacity(5, c2, () => state.c2Response);
});

Expand All @@ -144,6 +146,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should be idempotent
itShouldHaveCapacity(5, c2, () => state.c2Response);
});
});
Expand All @@ -166,6 +169,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should take into account leases on other orders
itShouldHaveCapacity(2, c1, () => state.c1Response);
});

Expand Down Expand Up @@ -211,6 +215,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should only take into account leases on other orders
itShouldHaveCapacity(2, c1, () => state.c1Response);
});

Expand All @@ -222,6 +227,7 @@ FeatureHelper.describeFeature(module, {
.successChecks()
.validationTests();

// it should only take into account leases on other orders
itShouldHaveCapacity(2, c2, () => state.c2Response);
});
});
Expand Down
Expand Up @@ -10,7 +10,7 @@ Coverage Status: **none**
### 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`.

[TestOpportunityBookableWithinValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableWithinValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x1
[TestOpportunityBookableWithinValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableWithinValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x2, [TestOpportunityBookableOutsideValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableOutsideValidFromBeforeStartDate) x3

*Note the test coverage for this feature is currently nonexistent. The test suite does not yet include non-stubbed tests for this feature.*

Expand All @@ -30,5 +30,6 @@ Update `default.json` within `packages/openactive-integration-tests/config/` as
| Identifier | Name | Description | Prerequisites per Opportunity Type |
|------------|------|-------------|---------------|
| [opportunity-in-range-c1-c2](./implemented/opportunity-in-range-c1-c2-test.js) | Running C1 and C2 for opportunity in range should succeed | Booking an opportunity within the specified booking window | [TestOpportunityBookableWithinValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableWithinValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x1 |
| [opportunity-outside-range-c1-c2](./implemented/opportunity-outside-range-c1-c2-test.js) | Running C1 and C2 for opportunity outside range should fail | Booking an opportunity outside the specified booking window | [TestOpportunityBookableOutsideValidFromBeforeStartDate](https://openactive.io/test-interface#TestOpportunityBookableOutsideValidFromBeforeStartDate) x3, [TestOpportunityBookable](https://openactive.io/test-interface#TestOpportunityBookable) x1 |


@@ -0,0 +1,75 @@
const chai = require('chai');
const chakram = require('chakram');
const { FeatureHelper } = require('../../../../helpers/feature-helper');
const { GetMatch, C1, C2 } = require('../../../../shared-behaviours');
const { Common } = require('../../../../shared-behaviours/common');

/**
* @typedef {import('chakram').ChakramResponse} ChakramResponse
*/

/**
* @param {C1|C2} stage
* @param {() => ChakramResponse} responseAccessor This is wrapped in a
* function because the actual response won't be available until the
* asynchronous before() block has completed.
*/
function itShouldReturn409Conflict(stage, responseAccessor) {
it('should return 409', () => {
stage.expectResponseReceived();
chakram.expect(responseAccessor()).to.have.status(409);
});
}

FeatureHelper.describeFeature(module, {
testCategory: 'restrictions',
testFeature: 'booking-window',
testFeatureImplemented: true,
testIdentifier: 'opportunity-outside-range-c1-c2',
testName: 'Running C1 and C2 for opportunity outside range should fail',
testDescription: 'Booking an opportunity outside the specified booking window',
testOpportunityCriteria: 'TestOpportunityBookableOutsideValidFromBeforeStartDate',
controlOpportunityCriteria: 'TestOpportunityBookable',
},
(configuration, orderItemCriteria, featureIsImplemented, logger, state, flow) => {
beforeAll(async () => {
await state.fetchOpportunities(orderItemCriteria);
});

describe('Get Opportunity Feed Items', () => {
(new GetMatch({
state, flow, logger, orderItemCriteria,
}))
.beforeSetup()
.successChecks()
.validationTests();
});

describe('C1', () => {
(new C1({
state, flow, logger,
}))
.beforeSetup()
.successChecks()
.validationTests();
});

describe('C2', () => {
const c2 = (new C2({
state, flow, logger,
}))
.beforeSetup()
.validationTests();

itShouldReturn409Conflict(c2, () => state.c2Response);
Common.itForOrderItemByControl(orderItemCriteria, state, c2, () => state.c2Response.body,
'should include an OpportunityOfferPairNotBookableError',
(feedOrderItem, responseOrderItem, responseOrderItemErrorTypes) => {
chai.expect(responseOrderItemErrorTypes).to.include('OpportunityOfferPairNotBookableError');
},
'should not include an OpportunityOfferPairNotBookableError',
(feedOrderItem, responseOrderItem, responseOrderItemErrorTypes) => {
chai.expect(responseOrderItemErrorTypes).not.to.include('OpportunityOfferPairNotBookableError');
});
});
});
@@ -1 +1,2 @@
export type OfferConstraint = (offer: import("../types/Offer").Offer, opportunity: import("../types/Opportunity").Opportunity, options?: import("../types/Options").Options) => boolean;
export const TestOpportunityBookableWithinValidFromBeforeStartDate: import("../types/Criteria").Criteria;
Expand Up @@ -61,3 +61,7 @@ export function mustBeWithinBookingWindow(offer: import("../types/Offer").Offer,
* @returns {boolean}
*/
export function hasCapacityLimitOfOne(opportunity: Opportunity): boolean;
/**
* @type {OpportunityConstraint}
*/
export function remainingCapacityMustBeAtLeastTwo(opportunity: import("../types/Opportunity").Opportunity): boolean;
@@ -1,4 +1,3 @@
export type OpportunityConstraint = (opportunity: import("../../types/Opportunity").Opportunity, options?: import("../../types/Options").Options) => boolean;
export type OfferConstraint = (offer: import("../../types/Offer").Offer, opportunity: import("../../types/Opportunity").Opportunity, options?: import("../../types/Options").Options) => boolean;
/**
* Internal criteria which almost implements https://openactive.io/test-interface#TestOpportunityBookable
Expand Down
@@ -1,7 +1,5 @@
const moment = require('moment');

const { TestOpportunityBookable } = require('./TestOpportunityBookable');
const { createCriteria } = require('./criteriaUtils');
const { InternalCriteriaFutureScheduledOpportunity } = require('./internal/InternalCriteriaFutureScheduledOpportunity');
const { remainingCapacityMustBeAtLeastTwo, createCriteria, mustBeWithinBookingWindow } = require('./criteriaUtils');

/**
* @typedef {import('../types/Criteria').OfferConstraint} OfferConstraint
Expand All @@ -10,25 +8,28 @@ const { createCriteria } = require('./criteriaUtils');
/**
* @type {OfferConstraint}
*/
function outsideValidFromBeforeStartDate(offer, opportunity, options) {
return (Array.isArray(offer.availableChannel) && offer.availableChannel.includes('https://openactive.io/OpenBookingPrepayment'))
&& offer.advanceBooking !== 'https://openactive.io/Unavailable'
&& (offer.validFromBeforeStartDate && moment(opportunity.startDate).subtract(moment.duration(offer.validFromBeforeStartDate)).isAfter(options.harvestStartTime));
function mustBeOutsideBookingWindow(offer, opportunity, options) {
return offer.validFromBeforeStartDate && !mustBeWithinBookingWindow(offer, opportunity, options);
}

/**
* Implements https://openactive.io/test-interface#TestOpportunityBookableOutsideValidFromBeforeStartDate
lukehesluke marked this conversation as resolved.
Show resolved Hide resolved
*/
const TestOpportunityBookableOutsideValidFromBeforeStartDate = createCriteria({
name: 'TestOpportunityBookableOutsideValidFromBeforeStartDate',
opportunityConstraints: [],
opportunityConstraints: [
[
'Remaining capacity must be at least two',
remainingCapacityMustBeAtLeastTwo,
],
],
offerConstraints: [
[
'Outside ValidFromBeforeStartDate',
outsideValidFromBeforeStartDate,
'Must be outside booking window',
mustBeOutsideBookingWindow,
],
],
includeConstraintsFromCriteria: TestOpportunityBookable,
includeConstraintsFromCriteria: InternalCriteriaFutureScheduledOpportunity,
});

module.exports = {
Expand Down
@@ -1,13 +1,24 @@
const { InternalTestOpportunityBookable } = require('./internal/InternalTestOpportunityBookable');
const { createCriteria, mustBeWithinBookingWindow } = require('./criteriaUtils');

/**
* @typedef {import('../types/Criteria').OfferConstraint} OfferConstraint
*/

/**
* @type {OfferConstraint}
*/
function mustHaveBookingWindowAndBeWithinIt(offer, opportunity, options) {
return offer.validFromBeforeStartDate && mustBeWithinBookingWindow(offer, opportunity, options);
}

const TestOpportunityBookableWithinValidFromBeforeStartDate = createCriteria({
name: 'TestOpportunityBookableWithinValidFromBeforeStartDate',
opportunityConstraints: [],
offerConstraints: [
[
'Must be within booking window',
mustBeWithinBookingWindow,
mustHaveBookingWindowAndBeWithinIt,
],
],
includeConstraintsFromCriteria: InternalTestOpportunityBookable,
Expand Down
13 changes: 11 additions & 2 deletions packages/test-interface-criteria/src/criteria/criteriaUtils.js
Expand Up @@ -50,7 +50,6 @@ function getType(opportunity) {
return opportunity['@type'] || opportunity.type;
}


/**
* @param {Opportunity} opportunity
* @returns {boolean}
Expand All @@ -75,7 +74,7 @@ function getRemainingCapacity(opportunity) {
*/
function mustBeWithinBookingWindow(offer, opportunity, options) {
if (!offer || !offer.validFromBeforeStartDate) {
return false;
return null; // Required for validation step
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, what's this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without it, validation fails, complaining that the string isn't a valid ISO offset. It would also work fine with false, but I wanted to make it explicit that it can't be within or outside the booking window if there is no booking window.

}

const start = moment(opportunity.startDate);
Expand All @@ -85,11 +84,21 @@ function mustBeWithinBookingWindow(offer, opportunity, options) {
return valid;
}

/**
* @type {OpportunityConstraint}
*/
function remainingCapacityMustBeAtLeastTwo(opportunity) {
// A capacity of at least 2 is needed for cases other than IndividualFacilityUse because the multiple OrderItem tests use 2 of the same item (via the opportunityReuseKey).
// The opportunityReuseKey is not used for IndividualFacilityUse, which is limited to a maximumUses of 1 by the specification.
return getRemainingCapacity(opportunity) > (hasCapacityLimitOfOne(opportunity) ? 0 : 1);
}

module.exports = {
createCriteria,
getId,
getType,
getRemainingCapacity,
mustBeWithinBookingWindow,
hasCapacityLimitOfOne,
remainingCapacityMustBeAtLeastTwo,
};
@@ -1,22 +1,12 @@
const moment = require('moment');

const { InternalCriteriaFutureScheduledOpportunity } = require('../internal/InternalCriteriaFutureScheduledOpportunity');
const { getRemainingCapacity, createCriteria, hasCapacityLimitOfOne } = require('../criteriaUtils');
const { remainingCapacityMustBeAtLeastTwo, createCriteria } = require('../criteriaUtils');

/**
* @typedef {import('../../types/Criteria').OpportunityConstraint} OpportunityConstraint
* @typedef {import('../../types/Criteria').OfferConstraint} OfferConstraint
*/

/**
* @type {OpportunityConstraint}
*/
function remainingCapacityMustBeAtLeastTwo(opportunity) {
// A capacity of at least 2 is needed for cases other than IndividualFacilityUse because the multiple OrderItem tests use 2 of the same item (via the opportunityReuseKey).
// The opportunityReuseKey is not used for IndividualFacilityUse, which is limited to a maximumUses of 1 by the specification.
return getRemainingCapacity(opportunity) > (hasCapacityLimitOfOne(opportunity) ? 0 : 1);
}

/**
* @type {OfferConstraint}
*/
Expand Down