Skip to content

Commit

Permalink
requirements -> expectations
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehesluke committed Oct 17, 2024
1 parent 9e19d3f commit 33df93b
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 55 deletions.
2 changes: 1 addition & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
}
},
"useShapeExpressions": false,
"useListenerItemRequirements": false
"ignoreUnexpectedFeedUpdates": false
},
"sellers": {
"primary": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ async function createOrderListenerApi(req, res) {
}
if (!error400IfExpressParamsAreMissing(req, res, ['type', 'bookingPartnerIdentifier', 'uuid'])) { return; }
const { type, bookingPartnerIdentifier, uuid } = req.params;
const itemRequirements = req.body?.itemRequirements ?? [];
const itemExpectations = req.body?.itemExpectations ?? [];
const listenerId = TwoPhaseListeners.getOrderListenerId(/** @type {OrderFeedType} */(type), bookingPartnerIdentifier, uuid);
if (!error409IfListenerAlreadyExists(res, state.twoPhaseListeners.byOrderUuid, type, listenerId)) { return; }
state.twoPhaseListeners.byOrderUuid.set(listenerId, TwoPhaseListeners.createNewListener(itemRequirements));
state.twoPhaseListeners.byOrderUuid.set(listenerId, TwoPhaseListeners.createNewListener(itemExpectations));
const feedContext = state.feedContextMap.get(
orderFeedContextIdentifier(
type === 'orders' ? ORDERS_FEED_IDENTIFIER : ORDER_PROPOSALS_FEED_IDENTIFIER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ const { isEqual, isNil } = require('lodash');
* @typedef {import('../models/core').OrderFeedType} OrderFeedType
*/
/**
* @typedef {object} ListenerItemRequirement
* As an example, with the following requirement:
* @typedef {object} ListenerItemExpectation
* As an example, with the following expectation:
* ```
* {
* jsonPath: '$.data.orderedItem[*].orderItemStatus',
* checkType: 'allNotEqual',
* checkValue: 'https://openactive.io/OrderItemConfirmed'
* }
* ```
* This requirement will be satisfied by an Order, which, for example, has all
* This expectation will be satisfied by an Order, which, for example, has all
* cancelled OrderItems.
* @property {string} jsonPath A JSONPath query. This query will extract an
* array of specific values from the item. The extracted values will be
Expand All @@ -36,9 +36,9 @@ const { isEqual, isNil } = require('lodash');
*/
/**
* @typedef {object} Listener
* @property {ListenerItemRequirement[]} itemRequirements
* @property {ListenerItemExpectation[]} itemExpectations
* What kind of item to look for. If an item does not meet all the
* requirements, then it will be ignored. For example, for a Seller Requested
* expectations, then it will be ignored. For example, for a Seller Requested
* Cancellation test, a listener might be created which requires the Order to
* have all OrderItems set to cancelled, so that it can ignore irrelevant
* Order updates.
Expand Down Expand Up @@ -82,12 +82,12 @@ const TwoPhaseListeners = {
/**
* Listener that has just been created.
*
* @param {ListenerItemRequirement[]} itemRequirements
* @param {ListenerItemExpectation[]} itemExpectations
* @returns {Listener}
*/
createNewListener(itemRequirements) {
createNewListener(itemExpectations) {
return {
itemRequirements,
itemExpectations,
item: null,
collectRes: null,
};
Expand All @@ -96,12 +96,12 @@ const TwoPhaseListeners = {
* Listener which is awaiting response from a Broker API client.
*
* @param {import('express').Response} res
* @param {ListenerItemRequirement[]} itemRequirements
* @param {ListenerItemExpectation[]} itemExpectations
* @returns {Listener}
*/
createPendingListener(res, itemRequirements) {
createPendingListener(res, itemExpectations) {
return {
itemRequirements,
itemExpectations,
item: null,
collectRes: res,
};
Expand All @@ -110,20 +110,20 @@ const TwoPhaseListeners = {
* Listener whose item has been found but it is not yet awaiting response from a Broker API client.
*
* @param {Listener['item']} item
* @param {ListenerItemRequirement[]} itemRequirements
* @param {ListenerItemExpectation[]} itemExpectations
* @returns {Listener}
*/
createResolvedButNotPendingListener(item, itemRequirements) {
createResolvedButNotPendingListener(item, itemExpectations) {
return {
itemRequirements,
itemExpectations,
item,
collectRes: null,
};
},
/**
* For an item being harvested from RPDE, check if there is a listener listening for it.
*
* If yes, and the item meets the listener's requirements, respond to that listener.
* If yes, and the item meets the listener's expectations, respond to that listener.
*
* @param {ListenersMap} listenersMap
* @param {string} listenerId
Expand All @@ -133,17 +133,17 @@ const TwoPhaseListeners = {
// If there is a listener for this ID, either the listener map needs to be populated with the item or
// the collection request must be fulfilled
if (listenersMap.has(listenerId)) {
const { collectRes, itemRequirements } = listenersMap.get(listenerId);
const meetsRequirements = doesItemMeetItemRequirements(listenerId, itemRequirements, item);
if (!meetsRequirements) {
const { collectRes, itemExpectations } = listenersMap.get(listenerId);
const meetsExpectations = doesItemMeetItemExpectations(listenerId, itemExpectations, item);
if (!meetsExpectations) {
return;
}
// If there's already a collection request, fulfill it
if (collectRes) {
doRespondToAndDeleteListener(listenersMap, listenerId, collectRes, item);
} else {
// If not, set the opportunity so that it can returned when the collection call arrives
listenersMap.set(listenerId, TwoPhaseListeners.createResolvedButNotPendingListener(item, itemRequirements));
listenersMap.set(listenerId, TwoPhaseListeners.createResolvedButNotPendingListener(item, itemExpectations));
}
}
},
Expand All @@ -168,10 +168,10 @@ const TwoPhaseListeners = {
if (!listener) {
return false;
}
const { item, itemRequirements } = listener;
const { item, itemExpectations } = listener;
if (!item) {
// item has not yet been found, so listen for it
listenersMap.set(listenerId, TwoPhaseListeners.createPendingListener(res, itemRequirements));
listenersMap.set(listenerId, TwoPhaseListeners.createPendingListener(res, itemExpectations));
} else {
// item has already been found
doRespondToAndDeleteListener(listenersMap, listenerId, res, item);
Expand All @@ -196,14 +196,14 @@ function doRespondToAndDeleteListener(listenersMap, listenerId, res, item) {

/**
* @param {string} listenerId Used for error messages
* @param {ListenerItemRequirement[]} itemRequirements
* @param {ListenerItemExpectation[]} itemExpectations
* @param {unknown} item
*/
function doesItemMeetItemRequirements(listenerId, itemRequirements, item) {
if (itemRequirements.length === 0) {
function doesItemMeetItemExpectations(listenerId, itemExpectations, item) {
if (itemExpectations.length === 0) {
return true;
}
return itemRequirements.every(({ jsonPath, checkType, checkValue, checkArgs }, i) => {
return itemExpectations.every(({ jsonPath, checkType, checkValue, checkArgs }, i) => {
const extractedValues = jsonpath.query(item, jsonPath);
switch (checkType) {
case 'allNotEqual': {
Expand All @@ -218,7 +218,7 @@ function doesItemMeetItemRequirements(listenerId, itemRequirements, item) {
}
const { n } = checkArgs;
if (isNil(n)) {
throw new Error(`ListenerItemRequirement (index: ${i}, for listener ID: ${listenerId}) is missing \`n\` in \`checkArgs\` for \`atLeastNNotEqual\` check type`);
throw new Error(`ListenerItemExpectation (index: ${i}, for listener ID: ${listenerId}) is missing \`n\` in \`checkArgs\` for \`atLeastNNotEqual\` check type`);
}
const valuesWhichAreNotEqual = extractedValues.filter((value) => !isEqual(value, checkValue));
const amountWhichAreNotEqual = valuesWhichAreNotEqual.length;
Expand Down
12 changes: 7 additions & 5 deletions packages/openactive-integration-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,18 @@ If turned on, you may be able to build a simpler and more extensible:
"useShapeExpressions": false
```

### `useListenerItemRequirements`
### `ignoreUnexpectedFeedUpdates`

Whether or not to use the experimental Listener Item Requirements feature (introduced in this [issue](https://github.com/openactive/openactive-test-suite/issues/698)). This experimental feature is not advised as 1). it is not yet fully implemented across all tests, and 2). it may lead to confusing results when a test fails (as is discussed in the issue).
Whether or not to use the experimental Listener Item Expectations feature (introduced in this [issue](https://github.com/openactive/openactive-test-suite/issues/698)). This experimental feature is not advised as 1). it is not yet fully implemented across all tests, and 2). it may lead to confusing results when a test fails (as is discussed in the issue).

It is off by default.
It is `false` by default.

If turned on, [Broker Microservice](../openactive-broker-microservice/) will be more picky when listening for updates in the Orders Feeds or Opportunity Feeds. This is necessary if the booking system under test produces RPDE feed updates which are additional to (and irrelevant to) the updates that the tests are listening for.
If turned on, [Broker Microservice](../openactive-broker-microservice/) will be more picky when listening for updates in the Orders Feeds or Opportunity Feeds. Specifically, it will ignore feed updates which it was not expecting.

This is necessary if the booking system under test produces RPDE feed updates which are additional to (and irrelevant to) the updates that the tests are listening for.

```json
"useListenerItemRequirements": false
"ignoreUnexpectedFeedUpdates": false
```

## Reading test results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { OrderFeedUpdateFlowStageUtils } = require('./order-feed-update');
const { PFlowStage } = require('./p');
const { TestInterfaceActionFlowStage } = require('./test-interface-action');
const { fixCalculatedMoneyValue } = require('../money-utils');
const { ListenerItemRequirementRecipes } = require('../item-listener-requirements');
const { ListenerItemExpectationRecipes } = require('../listener-item-expectations');

/**
* @typedef {import('../logger').BaseLoggerType} BaseLoggerType
Expand Down Expand Up @@ -719,10 +719,10 @@ const FlowStageRecipes = {
statuses one at a time, rather than all at once.
(The Seller Requested Cancellation action is expected to cancel all
OrderItems)
Using ListenerItemRequirements, an update which does not have all
Using ListenerItemExpectations, an update which does not have all
OrderItem statuses changed will be ignored (if the config option is
enabled). */
listenerItemRequirements: [ListenerItemRequirementRecipes.allNonConfirmedOrderItems()],
listenerItemExpectations: [ListenerItemExpectationRecipes.allNonConfirmedOrderItems()],
},
});
const assertOpportunityCapacityAfterCancel = new AssertOpportunityCapacityFlowStage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ const { FlowStageUtils } = require('./flow-stage-utils');
* @param {RequestHelperType} args.requestHelper
* @param {OrderFeedType} args.orderFeedType
* @param {() => boolean} args.failEarlyIf
* @param {import('../item-listener-requirements').ListenerItemRequirement[]} [args.listenerItemRequirements]
* @param {import('../listener-item-expectations').ListenerItemExpectation[]} [args.listenerItemExpectations]
* @returns {Promise<ListenerOutput>}
*/
async function runOrderFeedListener({ uuid, requestHelper, orderFeedType, failEarlyIf, listenerItemRequirements }) {
async function runOrderFeedListener({ uuid, requestHelper, orderFeedType, failEarlyIf, listenerItemExpectations }) {
// If a previous stage has failed, don't bother listening for the expected feed update.
if (failEarlyIf()) {
throw new Error('failing early as a previous stage failed');
}
/* TODO allow specification of bookingPartnerIdentifier. Currently we don't
have any Order Feed Update tests which need to test anything other than the
primary bookingPartnerIdentifier so this hasn't yet been required. */
await requestHelper.postOrderFeedChangeListener(orderFeedType, 'primary', uuid, listenerItemRequirements);
await requestHelper.postOrderFeedChangeListener(orderFeedType, 'primary', uuid, listenerItemExpectations);
return {};
}

Expand Down Expand Up @@ -102,9 +102,9 @@ class OrderFeedUpdateListener extends FlowStage {
* @param {string} args.uuid
* @param {OrderFeedType} args.orderFeedType
* @param {() => boolean} args.failEarlyIf
* @param {import('../item-listener-requirements').ListenerItemRequirement[]} [args.listenerItemRequirements]
* @param {import('../listener-item-expectations').ListenerItemExpectation[]} [args.listenerItemExpectations]
*/
constructor({ prerequisite, uuid, requestHelper, orderFeedType, failEarlyIf, listenerItemRequirements }) {
constructor({ prerequisite, uuid, requestHelper, orderFeedType, failEarlyIf, listenerItemExpectations }) {
super({
prerequisite,
getInput: FlowStageUtils.emptyGetInput,
Expand All @@ -116,7 +116,7 @@ class OrderFeedUpdateListener extends FlowStage {
requestHelper,
orderFeedType,
failEarlyIf,
listenerItemRequirements,
listenerItemExpectations,
});
},
itSuccessChecksFn() { /* there are no success checks - these happen at the OrderFeedUpdateCollector stage */ },
Expand Down Expand Up @@ -217,7 +217,7 @@ const OrderFeedUpdateFlowStageUtils = {
* Generally, this will be something like `failEarlyIf: () => p.getOutput().httpResponse.response.statusCode >= 400`.
*
* Defaults to () => false (i.e. do not fail early).
* @param {import('../item-listener-requirements').ListenerItemRequirement[]} [args.orderFeedUpdateParams.listenerItemRequirements]
* @param {import('../listener-item-expectations').ListenerItemExpectation[]} [args.orderFeedUpdateParams.listenerItemExpectations]
* @returns {[wrappedStage: TWrappedFlowStage, orderFeedUpdateCollector: OrderFeedUpdateCollector]}
*/
wrap({ wrappedStageFn, orderFeedUpdateParams }) {
Expand All @@ -229,7 +229,7 @@ const OrderFeedUpdateFlowStageUtils = {
prerequisite: orderFeedUpdateParams.prerequisite,
orderFeedType,
failEarlyIf,
listenerItemRequirements: orderFeedUpdateParams.listenerItemRequirements,
listenerItemExpectations: orderFeedUpdateParams.listenerItemExpectations,
});
const wrappedStage = wrappedStageFn(listenForOrderFeedUpdate);
const collectOrderFeedUpdate = new OrderFeedUpdateCollector({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
* checkArgs?: {
* n?: number;
* };
* }} ListenerItemRequirement
* }} ListenerItemExpectation
*/

const ListenerItemRequirementRecipes = {
const ListenerItemExpectationRecipes = {
/**
* Use this to specifically wait for one or more (`n`) OrderItems to change
* their status from OrderItemConfirmed to something else.
*
* @param {number} n - The number of OrderItems that must not be confirmed.
* @returns {ListenerItemRequirement}
* @returns {ListenerItemExpectation}
*/
nonConfirmedOrderItems(n) {
return {
Expand All @@ -32,7 +32,7 @@ const ListenerItemRequirementRecipes = {
* Use this to specifically wait for ALL OrderItems to change their status
* from OrderItemConfirmed to something else.
*
* @returns {ListenerItemRequirement}
* @returns {ListenerItemExpectation}
*/
allNonConfirmedOrderItems() {
return {
Expand All @@ -44,5 +44,5 @@ const ListenerItemRequirementRecipes = {
};

module.exports = {
ListenerItemRequirementRecipes,
ListenerItemExpectationRecipes,
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const { MICROSERVICE_BASE, BOOKING_API_BASE, TEST_DATASET_IDENTIFIER, SELLER_CON

const OPEN_BOOKING_API_REQUEST_TIMEOUT = config.get('integrationTests.openBookingApiRequestTimeout');
const BROKER_MICROSERVICE_FEED_REQUEST_TIMEOUT = config.get('integrationTests.waitForItemToUpdateInFeedTimeout');
const USE_LISTENER_ITEM_REQUIREMENTS = config.has('integrationTests.useListenerItemRequirements')
? config.get('integrationTests.useListenerItemRequirements')
const IGNORE_UNEXPECTED_FEED_UPDATES = config.has('integrationTests.ignoreUnexpectedFeedUpdates')
? config.get('integrationTests.ignoreUnexpectedFeedUpdates')
: false;

const BROKER_CHAKRAM_REQUEST_OPTIONS = {
Expand Down Expand Up @@ -233,14 +233,14 @@ class RequestHelper {
* @param {'orders' | 'order-proposals'} type
* @param {string} bookingPartnerIdentifier
* @param {string} uuid
* @param {import('./item-listener-requirements').ListenerItemRequirement[]} [listenerItemRequirements]
* @param {import('./listener-item-expectations').ListenerItemExpectation[]} [listenerItemExpectations]
*/
async postOrderFeedChangeListener(type, bookingPartnerIdentifier, uuid, listenerItemRequirements) {
async postOrderFeedChangeListener(type, bookingPartnerIdentifier, uuid, listenerItemExpectations) {
return await this.post(
`Orders (${type}) Feed listen for '${uuid}' change (auth: ${bookingPartnerIdentifier})`,
`${MICROSERVICE_BASE}/order-listeners/${type}/${bookingPartnerIdentifier}/${uuid}`,
{
itemRequirements: USE_LISTENER_ITEM_REQUIREMENTS ? listenerItemRequirements : undefined,
itemExpectations: IGNORE_UNEXPECTED_FEED_UPDATES ? listenerItemExpectations : undefined,
},
BROKER_CHAKRAM_REQUEST_OPTIONS,
);
Expand Down

0 comments on commit 33df93b

Please sign in to comment.