diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ed50dc5..5d88ff0e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added utility functions `isStandardContextType(contextType: string)`, `isStandardIntent(intent: string)`,`getPossibleContextsForIntent(intent: StandardIntent)`. ([#1139](https://github.com/finos/FDC3/pull/1139)) * Added support for event listening outside of intent or context listnener. Added new function `addEventListener`, type `EventHandler`, enum `FDC3EventType` and interfaces `FDC3Event` and `FDC3ChannelChangedEvent`. ([#1207](https://github.com/finos/FDC3/pull/1207)) * Added new `CreateOrUpdateProfile` intent. ([#1359](https://github.com/finos/FDC3/pull/1359)) +* Added conformance tests into the FDC3 API documentation in the current version and backported into 2.0 and 2.1. Removed outdated 1.2 conformance tests (which are preserved in the older 2.0 and 2.1 versions). ([#1417](https://github.com/finos/FDC3/pull/1417)). ### Changed diff --git a/docs/api/conformance/App-Channel-Tests.md b/docs/api/conformance/App-Channel-Tests.md new file mode 100644 index 000000000..027cdb57e --- /dev/null +++ b/docs/api/conformance/App-Channel-Tests.md @@ -0,0 +1,62 @@ +--- +id: App-Channel-Tests +sidebar_label: App Channel Tests +title: App Channel Tests +hide_title: true +--- + +# App Channel Tests + + +## Basic Broadcast + +| App | Step | Details | +|-----|--------------------|----------------------------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| A | 2.Add Context Listener |Add an _untyped_ context listener to the channel, using:
!`await testChannel.addContextListener(null, handler)` | +| B | 3.Retrieve `Channel` | Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 4.Broadcast | Broadcast an `fdc3.instrument` Context to the channel with:
`testChannel.broadcast()`| +| A | 5.Receive Context | The handler added in step 2 will receive the instrument context. Ensure that the instrument received by A is identical to that sent by B. | + +- `ACBasicUsage1` Perform above test. + +## Current Context + +| App | Step | Details | +|-----|--------------------|----------------------------------------------------------------------------| +| B | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| B | 2.Broadcast | Broadcast an `fdc3.instrument` to the channel using:
`testChannel.broadcast()`| +| A | 3.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel B did (`test-channel`)| +| A | 4.Retrieve Current Context | A gets the _current context_ of the user channel. via: `await testChannel.getCurrentContext()`
Ensure that the instrument received by A is identical to that sent by B | + +- `ACBasicUsage2` Perform above test + +## Filtered Context + +| App | Step | Details | +|-----|--------------------|-----------------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| A | 2.Add Context Listener |Add an _typed_ context listener for `fdc3.instrument`, using:
`await testChannel.addContextListener("fdc3.instrument", handler)`| +| B | 3.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 4.Broadcast | B broadcasts both an `fdc3.instrument` context and an `fdc3.contact` context, using:
`testChannel.broadcast()`
`testChannel.broadcast()`| +| A | 5.Receive Context | An fdc3.instrument context is received by the handler added in step 2.
Ensure that the fdc3.instrument received by A is identical to that sent by B
Ensure that the fdc3.contact context is NOT received. | + +- `ACFilteredContext1`: Perform above test. +- `ACFilteredContext2`: Perform above test, but add listeners for both `fdc3.instrument` and `fdc3.contact` in step2. Ensure that both context objects are received. +- `ACFilteredContext3`: Perform above test, except creating a _different_ channel in app B. Check that you _don't_ receive anything (as the channels don't match). +- `ACFilteredContext4`: Perform above test, except that after creating the channel **A** creates another channel with a further _different_ channel id and adds a further context listener to it. Ensure that **A** is still able to receive context on the first channel (i.e. it is unaffected by the additional channel) and does NOT receive anything on the second channel. +- `ACUnsubscribe`: Perform above test, except that after creating the channel **A** then `unsubscribe()`s the listener it added to the channel. Check that **A** does NOT receive anything. + +### App Channel History + +| App | Step | Details | +|-----|--------------------|---------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| B | 2.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 3.Broadcast |B broadcasts both the instrument context and a contact context, using:
`testChannel.broadcast()`
`testChannel.broadcast()` | +| A | 4.Add Context Listener| A adds a context listener to the channel _after_ B has completed all its broadcasts, via:
`await testChannel.addContextListener("fdc3.instrument", handler)`
Ensure that A does NOT receive any context via these listeners (past context is only retrieved via a `getCurrentContext()` call on App channels). | +| A | 5.Retrieve Current Context | A is able to retrieve the most recent context of each context type from the `Channel` via:
`const instrument = await testChannel.getCurrentContext('fdc3.instrument')`
`const instrument = await testChannel.getCurrentContext('fdc3.contact')`
Ensure that both contexts retrieved by A are identical to those sent by B| + +- `ACContextHistoryTyped`: Perform above test. +- `ACContextHistoryMultiple`: **B** Broadcasts multiple history items of both types. Ensure that only the last version of each type is received by **A**. +- `ACContextHistoryLast`: In step 5. **A** retrieves the _untyped_ current context of the channel via `const currentContext = await testChannel.getCurrentContext()`. Ensure that A receives only the very last broadcast context item _of any type_. diff --git a/docs/api/conformance/Basic-Tests.md b/docs/api/conformance/Basic-Tests.md new file mode 100644 index 000000000..d58c8b3e2 --- /dev/null +++ b/docs/api/conformance/Basic-Tests.md @@ -0,0 +1,33 @@ +--- +id: Basic-Tests +sidebar_label: Basic Tests +title: Basic Tests +hide_title: true +--- + +# Basic Tests + + +_These are some basic sanity tests implemented in the FDC3 Conformance Framework. It is expected that Desktop Agent testers will run these first before commencing the much more thorough tests in section 2 onwards._ + +- `BasicCL1`: You can create a context listener by calling `fdc3.addContextListener('fdc3.contact',)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicCL2`: You can create an **unfiltered** context listener by calling `fdc3.addContextListener(null,)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicIL1`: You can create an intent listener by calling `fdc3.addIntentListener(,)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicGI1`: An application can retrieve an `ImplementationMetadata` object to find out the version of FDC3 it is using and the provider details by calling: + - `await fdc3.getInfo()` +- `BasicAC1`: An application can retrieve a named 'App' channel via the `fdc3.getOrCreateChannel()` function. The `Channel` object returned conforms to the defined interface. +- `BasicUC1`: An application can query the available user/system channels, which are returned as an array of `Channel` Objects conforming to the defined interface. The API call is: + - `await fdc3.getUserChannels()` +- `BasicJC1`: The application should be able to join one of the user/system channels with the channel's id. Having done so, the current channel should NOT be null, and be set for the application _to the channel for the id given_. After you leave the current channel, it should go back to being `null`. + - The channel is joined with: + - `fdc3.joinUserChannel()` + - A `Channel` object representing the current channel is retrieved with: + - `fdc3.getCurrentChannel()` to get the current channel. + - The channel is left with: + - `fdc3.leaveCurrentChannel()` +- `BasicRI1`: The application should be able to raise an intent by invoking: + - `fdc3.raiseIntent()` + - A promise should be returned. +- `BasicRI2`: The application should be able to raise an intent for some item of context by invoking: + - `fdc3.raiseIntentForContext()` + - A promise should be returned. diff --git a/docs/api/conformance/Intents-Tests.md b/docs/api/conformance/Intents-Tests.md new file mode 100644 index 000000000..3d1c15ed2 --- /dev/null +++ b/docs/api/conformance/Intents-Tests.md @@ -0,0 +1,244 @@ +--- +id: Intents-Tests +sidebar_label: Intents Tests +title: Intents Tests +hide_title: true +--- + +# Intents Tests + + +_Please note that API calls (and associated test cases) relating to API calls based on the `name` property of an appD record (used to specify a target application) were deprecated in FDC3 2.0 in favour of those based on `AppIdentifier`. Hence, those API calls have become optional and test cases related to them have been removed._ + +## Setup + +We assume 6 context types in the below tests (and associated AppD records): + +- `testContextX` +- `testContextY` +- `testContextZ` +- `nonExistentContext` (context object with a unique type that does NOT appear in any of the apps (metadata or otherwise). +- `privateChannelDetails` +- `privateChannelIsPrivateResult` + +These may be used in a test as a context object `{ "type": "" }` or just the base type name. Where the base type name is used it is surround with "quotes". If not wrapped in quotes assume it is an instance of that context type (generally just an object with a `type` field set to the type name - but occasionally with other data). + +You will need to pre-populate the AppDirectory with the following items (some of which will never be started, but must be configured to confirm correct behavior from various API functions): + +| App | Usage | ListensFor `(pattern: intent([context-types…]) (=> result-type)`) | On Startup | +|-----|-------------------------------------------------------|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| A | Raise Intent tests without results | `aTestingIntent(testContextX,testContextZ)`
`sharedTestingIntent1(testContextX)` | addIntentListener() for given intents | +| B | Raise Intent tests with Context results | `bTestingIntent(testContextY)`
`sharedTestingIntent1(testContextX, testContextY) => testContextY` | addIntentListener() for given intents | +| C | Find Intent tests (never started) | `cTestingIntent(testContextX) => testContextZ` | addIntentListener() for given intents | +| D | Find Intent tests (never started) | `sharedTestingIntent2(testContextX) => testContextZ` | addIntentListener() for given intents | +| E | Find Intent & Raise Intent with Channel result | `sharedTestingIntent2(testContextY) => channel` | addIntentListener() for given intents | +| F | Find Intent & Raise Intent with PrivateChannel result | `sharedTestingIntent2(testContextY) => channel` * | addIntentListener() for given intents | +| G | Find Intent tests (never started) | `sharedTestingIntent2(testContextY)` | addIntentListener() for given intents | +| H | Raise Intent (bad config/behavior) | `sharedTestingIntent2(testContextY) => testContextZ` | - no action | +| I | Raise Intent (bad config/behavior) | `sharedTestingIntent2(testContextY) => testContextZ` | addIntentListener(‘MadeUpIntent’, handler) | +| J | PrivateChannels are private | `privateChannelIsPrivate(privateChannelDetails) => privateChannelIsPrivateResult` | Tries to retrieve privateChannel sent in the privateChannelDetails context, fails | +| K | PrivateChannel lifecycle events | `kTestingIntent(testContextX) => channel` | addIntentListener() for given intents | + +NB: + +- There is no way to indicate in the app directory the difference between a private channel and app channel. +- We assume a final test app `Test` that will discover the Intent support in the others using the API. + +Finally, please note that this is a larger set of apps than were required for 1.2 tests. This is due to an increased number of parameters to API calls and AppD records, which multiplies the number of apps required. The apps are all specified here (rather than broken down over multiple issues) to ensure that clashes between test case sets can be worked out here. For example, adding one additional app that works with a particular intent/context pair might corrupt the results of multiple `findIntent` or `raiseIntent` tests. Hence, please stick to the defined type and report any issues you find so that they can be rectified in these definitions. + +## Find Intent basic usage + +- `2.0-FindIntentAppD`: Calls `fdc3.findIntent("aTestingIntent")`. Receives promise containing an appIntent with metadata containing `aTestingIntent` and only **A** `AppMetadata`. +- `2.0-FindNonExistentIntentAppD`: Calls `fdc3.findIntent("nonExistentIntent")`. Rejects with an Error whose `message` is [`ResolveError.NoAppsFound`](https://fdc3.finos.org/docs/api/ref/Errors#resolveerror) +- `2.0-FindIntentAppDRightContext`: Calls `fdc3.findIntent("aTestingIntent", "testContextX")`. Receives promise containing an `AppIntent` with metadata containing `aTestingIntent` and only metadata for app **A**. +- `2.0-FindIntentAppDWrongContext`: Calls `fdc3.findIntent("aTestingIntent", "testContextY")`. Rejects with an Error whose `message` is [`ResolveError.NoAppsFound`](https://fdc3.finos.org/docs/api/ref/Errors#resolveerror) +- `2.0-FindIntentAppDMultiple1`: Calls `fdc3.findIntent("sharedTestingIntent2")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and metadata for apps **D**, **E**, **F**, **G**, **H** and **I** only. +- `2.0-FindIntentAppDMultiple2`: Calls `fdc3.findIntent("sharedTestingIntent2", "testContextY")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and `AppMetadata` for apps **E**, **F**, **G**, **H** and **I** only. + +## Find Intents By Context + +- `2.0-FindIntentByContextSingleContext`: Call `fdc3.findIntentsByContext(testContextX)`. Should return: + - `aTestingIntent` (app **A**), + - `sharedTestingIntent1` (**A**, **B**) + - `cTestingIntent` (**C**), + - `sharedTestingIntent2` (**D**) + - `kTestingIntent` (**K**), + - AND nothing else. +- `2.0FindIntentByContextWrongIntentAppD`: Calls `fdc3.findIntentsByContext(nonExistentContext)`. Rejects with an Error whose `message` is [`ResolveError.NoAppsFound`](https://fdc3.finos.org/docs/api/ref/Errors#resolveerror) + +## Find Intents By Result Type + +- `2.0-FindIntentAppDByResultSingle`: Calls `fdc3.findIntent("cTestingIntent", testContextX, "testContextZ")`. Receives promise containing an `AppIntent` with metadata containing `cTestingIntent` and only **C** app metadata. +- `2.0-FindIntentAppDByResultSingleNullContext`: Calls `fdc3.findIntent("cTestingIntent", null, "testContextZ")`. Receives promise containing an `AppIntent` with metadata containing `cTestingIntent` and only **C** app metadata. +- `2.0-FindIntentAppDByResultMultiple`: Calls `fdc3.findIntent("sharedTestingIntent1", testContextX, "testContextY")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent1` and only **B** app metadata. +- `2.0-FindIntentAppDByResultChannel1`: Calls `fdc3.findIntent("sharedTestingIntent2", testContextY, "channel")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and only **E** and **F** app metadata. +- `2.0-FindIntentAppDByResultChannel2`: Calls `fdc3.findIntent("sharedTestingIntent2", testContextY, "channel")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent1` and only **F** app metadata. + +## Raise Intent (Ignoring any result) + +| App | Step | Details | +|-------|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent1")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set.** | + +- `2.0-RaiseIntentSingleResolve`: Perform above test +- `2.0-RaiseIntentTargetedAppResolve`: Repeat the above test, but: + - In the first step use `fdc3.raiseIntent("sharedTestingIntent1", testContextX, {"appID": ""})` to start app B, + - Otherwise, as above. +- `2.0-RaiseIntentTargetedInstanceResolveOpen`: Repeat the above test, but: + - Before the first step, use `let appIdentifier = await fdc3.open({appId: ""})` to start A and retrieve its `AppIdentifier` with instance details. + - Then in the first step, use `fdc3.raiseIntent("aTestingIntent", testContextX, appIdentifier)` to target the running instance of app A. + - Confirm that the intent is delivered to the correct instance and that another instance is NOT started. Otherwise, as above. +- `2.0-RaiseIntentTargetedInstanceResolveFindInstances`: Repeat the above test, but: + - Before the first step, use `let appIdentifier = await fdc3.open({appId: ""})` to start A. + - Then use `const instances = await fdc3.findInstances({appId: ""})` to retrieve a list of instances of app A. Confirm that only one is present and retrieve its `AppIdentifier`, confirming that it contains an `instanceId` field that matches that returned by the `fdc3.open` call. + - Then in the first step, use `fdc3.raiseIntent("aTestingIntent", testContextX, appIdentifier)` to target the running instance of app A. + - Confirm that the intent is delivered to the correct instance and that another instance is NOT started. Otherwise, as above. +- `2.0-RaiseIntentFailedResolve`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextY)`. Note that no app supports this intent and context combination.** + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound`. +- `2.0-RaiseIntentFailTargetedAppResolve1`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextY, {appId: ""})`. + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound`. +- `2.0-RaiseIntentFailTargetedAppResolve2`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextX, {appId: "NonExistentApp"})`. + - You should receive a JavaScript Error with the message `ResolveError.TargetAppUnavailable`. +- `2.0-RaiseIntentFailTargetedAppResolve3`: Perform above test, but: + - Use `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`. + - You should receive a JavaScript Error with the message `ResolveError.IntentDeliveryFailed` (as this app is configured for the intent and context pair, but does not add any intent listeners). + - **Note: Test will need an extended timeout to allow for this to be returned in time by the desktop agent, which will have a vendor-defined timeout.** +- `2.0-RaiseIntentFailTargetedAppResolve4`: Perform above test, but: + - `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})` + - You should receive a JavaScript Error with the message `ResolveError.IntentDeliveryFailed` (as this app is configured for the intent and context pair, but adds intent listeners of the wrong type. + - **Note: Test will need an extended timeout to allow for this to be returned in time by the desktop agent, which will have a vendor-defined timeout.** +- `2.0-RaiseIntentFailTargetedAppInstanceResolve1`: Perform above test, but: + - First spawn an instance of App **A** and collect its `AppIdentifier` with `const appIdentifier = await fdc3.open({appId: ""})`. + - Then use `fdc3.raiseIntent("aTestingIntent", testContextY, appIdentifier)` to target that instance. + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound` (since A doesn't support this context type). +- `2.0-RaiseIntentFailTargetedAppInstanceResolve2`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextX, {appId: "", instanceId "NonExistentInstanceId"})`. + - You should receive a JavaScript Error with the message `ResolveError.TargetInstanceUnavailable`. + +## Raise Intent Result (void result) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| A | 5. return void | A should return `void` after a short delay (e.g. 5 seconds). | +| Test | 6. receive void result | The promise received by Test from `resolution.getResult()` should resolve to void. Confirm that the promise could be retrieved before the handler function returned and that the result was received _after_ the result was returned by A, NOT before. I.e. confirm that `resolution.getResult()` does NOT block until the result is returned, but rather returns a promise that can be awaited. | + +- `2.0-RaiseIntentVoidResult5secs`: Perform above test +- `2.0-RaiseIntentVoidResult0secs`: Perform above test, but A should return its result immediately (no delay). Ignore test step 6 (as there is too little time between the IntentResolution and IntentHandler completing). +- `2.0-RaiseIntentVoidResult61secs`: Perform above test, but A should return its result **after 61 seconds** (arbitrary delay to test timeout does NOT occur) + +## Raise Intent Result (Context result) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("sharedTestingIntent1", testContextY)`
starts app **B**. | +| B | 2. Receive Intent & Context | After starting up, B runs `fdc3.addIntentListener("sharedTestingIntent1")` to register its listener.
It then receives `testContextY`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App B's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| B | 5. return `testContextY` | B should return a `testContextY` instance after a short delay (e.g. 5 seconds). | +| Test | 6. receive context result | The promise received by Test from `resolution.getResult()` should resolve to the `testContextY` instance. Confirm that the promise could be retrieved before the handler function returned and that the result was received _after_ the result was returned by B, NOT before. I.e. confirm that `resolution.getResult()` does NOT block until the result is returned, but rather returns a promise that can be awaited. | + +- `2.0-RaiseIntentContextResult5secs`: Perform the above test. +- `2.0-RaiseIntentContextResult0secs`: Perform the previous test but B should return its result immediately (no delay). +- `2.0-RaiseIntentContextResult61secs`: As above, but B should return its result **after 61 seconds** (arbitrary delay to test timeout does NOT occur) + +## Raise Intent Result (Channel results) + +| App | Step | Details | +|-------|-----------------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise Intent | Test raises an intent with `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`
starts app E. | +| E | 2. Receive Intent & Context | After starting up, E runs `fdc3.addIntentListener("sharedTestingIntent2")` to register its listener.
It them receives `testContextY`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App E's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| E | 5. return Channel | E should retrieve a Channel object via `fdc3.getOrCreateChannel("someChannelName")` and return it immediately. | +| Test | 6. receive Channel result | The promise received by Test from `resolution.getResult()` should resolve to a `Channel` object with the expected id. Confirm that the `type` of the Channel object is "app". | +| Test | 7. addContextListener | Add a context listener to the Channel object via `channelObj.addContextListener("testContextZ", handler)` | +| E | 8. broadcast context | After a short delay (of a few seconds) E should broadcast a `testContextZ` context object over the channel, including an `id` field with a unique identifier set (e.g. a uuid). | +| Test | 9. receive context | Test should receive the context broadcast by E and confirm that it contains the expected `id` value. | + +- `2.0-RaiseIntentChannelResult`: Perform the above test +- `2.0-RaiseIntentPrivateChannelResult`: Perform the above test, but: + - Substitute app F throughout - which returns a PrivateChannel result instead of channel. + - At step 5, the PrivateChannel should be created via`fdc3.createPrivateChannel()`. + - At step 6 confirm that the type of the channel is "private". + +## PrivateChannels cannot be accessed as app channels + +| App | Step | Details | +|-------|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Create a private channel | Test creates a PrivateChannel with `const privChan = await fdc3.createPrivateChannel()`. Confirm that the Channel has an `id`. | +| Test | 2. Confirm private channel `id` is unique | Test creates a second PrivateChannel with `const privChan = await fdc3.createPrivateChannel();`. Confirm that the Channel has an `id` and that it is distinct from the first channel created. | +| Test | 3. Retrieve as app channel | Attempt to retrieve the channels as App Channels with `const appChan = await fdc3.getOrCreateChannel(privChan.id)` this should fail with `ChannelError.AccessDenied` | +| Test | 4. Raise Intent & await result | Start app J and pass it the id of the second PrivateChannel with `fdc3.raiseIntent("privateChannelIsPrivate", privateChannelDetails)`, where the context object contains the id of the channel to attempt to retrieve. An IntentResolution should be returned and App J should start. Wait for a result to be returned via `await resolution.getResult()`. | +| J | 5. Receive Intent & Context | J should add an Intent Listener and receive the context with `fdc3.addIntentListener("privateChannelIsPrivate", handler)` | +| J | 6. Retrieve as app channel | J should attempt to retrieve the channel as an App Channel by `id` with `const appChan = await fdc3.getOrCreateChannel("")` this should fail with `ChannelError.AccessDenied`. Return a `privateChannelIsPrivateResult` back to Test to complete the test. | +| Test | 7. Receive result | Test receives the result back from J and confirms that the test was passed. | + +- `2.0-PrivateChannelsAreNotAppChannels`: Perform the above test + +## PrivateChannel Lifecycle Events + +| App | Step | Details | +|-------|-----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise intent | Test raises an intent with `fdc3.raiseIntent(‘"kTestingIntent", testContextX, {appId: ""})`
starts app K. | +| K | 2. Receive Intent & Context | After starting up, K runs `fdc3.addIntentListener("kTestingIntent")` to register its listener.
It them receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App K's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| K | 5. Create PrivateChannel and setup event listeners | K should create a `PrivateChannel` object via `const privChan = await fdc3.createPrivateChannel()`,
it should then add listeners for the 3 events offered + a context listener via:
- `const listener1 = await privChan.onAddContextListener(handler1);`
- `const listener2 = await privChan.onUnsubscribe(handler2);`
- `const listener3 = await privChan.onDisconnect(handler3);`
- `const listener4 = await privChan.addContextListener("testContextX", handler4)`
it should then return the `PrivateChannel`. | +| Test | 6. receive PrivateChannel | The promise received by Test from `resolution.getResult()` should resolve to a `PrivateChannel` object. Confirm that the `type` of the Channel object is "private". | +| Test | 7. addContextListener | Test should add a context listener to the PrivateChannel object via `const listener1 = privChan.addContextListener("testContextZ", handler)` | +| K | 8. Receive event & broadcast context | The `onAddContextListener` handler (`listener1`) added in step 5 should fire after Test adds its listener. Once it has, K should broadcast a short stream of `testContextZ` objects, with consecutive integer values in them (e.g. 1-5). | +| Test | 9. Unsubscribe listener | Test should confirm receipt of the expected context objects, in the expected order, broadcast by K. It should then remove its context listener with `listener1.unsubscribe().` | +| K | 10. Receive unsubscribe event | The event handler registered by K via `onUnsubscribe` should fire. If it does not and the test moves to a subsequent step, K should indicate this to the test runner (failing the test).| +| Test | 11. Broadcast context | Test should broadcast at least one `testContextX` object via the PrivateChannel (back to K). | +| K | 12. Receive context | K should confirm receipt of the expected context. If it does not and the test moves to a subsequent step K should indicate this to the test runner (failing the test).| +| Test | 13. re-run addContextListener | Test should (again) add a context listener to the PrivateChannel object via `const listener2 = privChan.addContextListener("testContextZ", handler)` | +| K | 14. Receive event & broadcast context | The `onAddContextListener` handler added in step 5 should (again) fire after Test adds its listener. Once it has, K should again broadcast a short stream of `testContextZ` objects, with consecutive integer values in them (e.g. 6-10). | +| Test | 15. Disconnect | Test should (again) confirm receipt of the expected context objects, in the expected order, broadcast by K. It should then disconnect from the channel with [`privChan.disconnect().`](https://fdc3.finos.org/docs/api/ref/PrivateChannel#disconnect) | +| K | 16. Receive events & cleanup | The `onUnsubscribe` handler added in step 5 should (again) fire after Test calls `privChan.disconnect()`. Subsequently, the `onDisconnect` handler also added in step 5 should fire. Once it has, K can unsubscribe its listeners, indicate to the test runner that all steps were completed and close. | + +- `2.0-PrivateChannelsLifecycleEvents`: Perform the above test. + +## Resolving Ambiguous Intents + +FDC3 Desktop Agent MUST provide a method of resolving ambiguous intents (i.e. those that might be resolved by multiple applications) or unspecified intents (calls to raiseIntentForContext that return multiple options). This is often accomplished by providing a user interface allowing the user to select the desired target application or intent and application. + +As the methods of resolving ambiguous intents are often user interactive, it is either difficult or impossible to implement an automated test for this. Hence, manual tests should be performed as a final step in a conformance test. These tests are based on the same applications defined for and used in other intent tests - however a separate manual test app should be provided to enable the test. + +| App | Step | Details | +|---|---|---| +| Test | 1. Raise Ambiguous Intent | `fdc3.raiseIntent("sharedTestingIntent2", testContextY)` | +| User | 2. Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E`,`F`,`G`,`H` and `I`. | + +- `2.0-ResolveAmbiguousIntentTarget`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Raise Ambiguous Intent | `fdc3.raiseIntentForContext(testContextY)` | +| User | 2. Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E`,`F`,`G`,`H` and `I`. | + +- `2.0-ResolveAmbiguousContextTarget`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Open 4 Apps | Use `fdc3.open()` to open 2 instances of App `E` and 2 instances of `F`. | +| Test | 2. Raise Ambiguous Intent | `fdc3.raiseIntent("sharedTestingIntent2", testContextY)` | +| User | 3. Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E (1)`,`F (1)`,`E (2)`,`F (2)` and options to open `G`, `H` and `I` | + +- `2.0-ResolveAmbiguousIntentTargetMultiInstance`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Open 4 Apps | Use `fdc3.open()` to open 2 instances of App `E` and 2 instances of `F`. | +| Test | 2. Raise Ambiguous Intent | `fdc3.raiseIntentForContext(testContextY)` | +| User | 3. Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E (1)`,`F (1)`,`E (2)`,`F (2)` and options to open `G`, `H` and `I` | + +- `2.0-ResolveAmbiguousContextTargetMultiInstance`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. diff --git a/docs/api/conformance/Metadata-Tests.md b/docs/api/conformance/Metadata-Tests.md new file mode 100644 index 000000000..29ebac028 --- /dev/null +++ b/docs/api/conformance/Metadata-Tests.md @@ -0,0 +1,69 @@ +--- +id: Metadata-Tests +sidebar_label: Metadata Tests +title: Metadata Tests +hide_title: true +--- + +# Metadata & Instance Test Cases + + +You will need to pre-populate the AppDirectory with the following items: + +| App | Required Metadata | +|-----|------------------------------------------| +| A | Generic AppD Record which contains at least the following fields:
- `name`
- `version`
- `title`
- `tooltip`
- `description`
- `icons` (`Array`)
- `screenshots` (`Array`)
- `interop.intents.listensFor` (`aTestingIntent` with at least context type `testContextX`) | + +## Retrieve `AppMetadata` + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.getAppMetadata | Retrieve metadata for the configured app A with
`const metadata1 = await fdc3.getAppMetadata({appId: ""})` | +| Test | 2.Confirm | Compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. An `instanceId` should NOT be set | + +- `GetAppMetadata`: perform the above steps. + +## Instance Metadata + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Open a first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 3.getAppMetadata1 | Retrieve metadata for the first instance of the app with
`const metadata1 = fdc3.getAppMetadata(appIdentifier1)` | +| Test | 4.Confirm1 | Compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. | +| Test | 5.getAppMetadata2 | Retrieve metadata for the second instance of the app with
`const metadata2 = fdc3.getAppMetadata(appIdentifier2)` | +| Test | 6.Confirm2 | An `instanceId` should be provided, confirm that it matches the one in `appIdentifier2` | + +- `AppInstanceMetadata`: Perform the above steps. + +## Finding Instances + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Open the first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 3.FindInstances | Retrieve details of open instances of app A with
`let instances = await fdc3.findInstances({appId: ""})`
confirm that both `appIdentifier1` and `appIdentifier2` are both present in the array. | +| Test | 4.RaiseIntent | Use `appIdentifier1` to raise an intent and target that instance, with
`const resolution = fdc3.raiseIntent("aTestingIntent", {"type": "testContextX"}, appIdentifier1)` | +| Test | 5.Confirm1 | Check that `resolution.source` matches `appIdentifier1` | +| A | 6.ConfirmReceipt | Ensure that the instance of app A represented by `appIdentifier1` received the raised intent | + +- `FindInstances`: Perform the above steps. + +## Getting Info For The Agent + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.getInfo |Retrieve the `ImplementationMetadata` for the DesktopAgent with
- `fdc3.getInfo().then((implMetadata) => { subsequent steps }`
**Note that the use of `then` is deliberate and intended to confirm that a promise returned (as this function switched from synchronous to asynchronous in 2.0)**| +| Test | 2.CheckVersion | Check that the `fdc3Version` variable is present and at or greater than:
- 2.0
(which you can do with the [`versionIsAtLeast` function from FDC3's Methods.ts](https://github.com/finos/FDC3/blob/add64f8302c6dcdc8437cf0e245101e927b69ec2/src/api/Methods.ts#L207):
`const isFDC3v2 = versionIsAtLeast(implMetadata, "2.0")` | +| Test | 3.CheckProvider | Check that the `provider` variable is present and not an empty string | +| Test | 4.CheckFeatures | Check that the `optionalFeatures`, `optionalFeatures.OriginatingAppMetadata` and `optionalFeatures.UserChannelMembershipAPIs` variables are all present and that the latter two provide boolean values | + +- `GetInfo1`: Perform the above steps. + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Start an instance of App A with
`const appIdentifier1 = await fdc3.open({appId: ""})`
retrieve its `AppIdentifier` with instance details. Confirm that the `AppIdentifier` contains both an `appId` and `instanceId` | +| A | 2.getInfo | Retrieve the `ImplementationMetadata` for the DesktopAgent with:
`fdc3.getInfo().then((implMetadata) => { ... subsequent steps ...}`
This should include `AppMetadata` for the retrieving app. | +| A + Test | 3.Confirm | Check that `implMetadata.appMetadata` contains an `appId` and `instanceId` matching that retrieved in the first step (will require transmission of the details from A to Test or vice-versa). Also compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. | + +- `GetInfo2`: Perform the above steps. diff --git a/docs/api/conformance/Open-Tests.md b/docs/api/conformance/Open-Tests.md new file mode 100644 index 000000000..08cb9eb62 --- /dev/null +++ b/docs/api/conformance/Open-Tests.md @@ -0,0 +1,45 @@ +--- +id: Open-Tests +sidebar_label: Open Tests +title: Open Tests +hide_title: true +--- + +# Open Tests + + +## A Opens B + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App | App A calls a function (see below) to open a second app, B | +| A | 2. Check Metadata | Ensure that the correct app was opened | + +- `AOpensB3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - `fdc3.open({appId: “”})` +- `AOpensB4`: **A** uses an `AppIdentifier` to open B and retrieves an updated `AppIdentifier` with an `instanceId` set via `const instanceIdentifier = await fdc3.open({appId: “”})`. Ensure that the `appId` matches that requested and that an `instanceId` property has been set. + +## A Fails To Open Another App + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App | App A calls a function (see below) to try and open a non-existent app | +| A | 2. Check Error Response | `fdc3.open` returns a promise that rejects with an Error with the message "App Not Found" | + +- `AFailsToOpenB3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - `fdc3.open({appId: “”})` + +## A Opens B With Context + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App |App A opens app B with an `fdc3.instrument` Context Object by calling a function (see below) | +| B | 2. Receive Context | Add an untyped context listener via:
`fdc3.addContextListener(null, handler)`
B receives an `fdc3.instrument` Context Object matching that passed to the `fdc3.open() call made by A | + +- `AOpensBWithContext3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - `fdc3.open({appId: “”}, )` +- `AOpensBWithSpecificContext`: Perform AOpensBWithContext3 but replace **B**s call with `fdc3.addContextListener('fdc3.instrument', handler)` +- `AOpensBMultipleListen`: Perform `AOpensBWithSpecificContext` but **B** should perform an additional `fdc3.addContextListener('fdc3.contact', handler)` prior to the existing `addContextListener` for `fdc3.instrument`. The correct context listener should receive the context, and the promise completes successfully. +- `AOpensBWithWrongContext`: Perform `AOpensBWithSpecificContext` but **B** should add a context listener for the wrong context type (e.g. `fdc3.dummyType`) instead of the expected type in step 2. + - Confirm that NO context is received. + - The promise returned to **A** by `fdc3.open` rejects with an Error with message `AppTimeout` diff --git a/docs/api/conformance/Overview.md b/docs/api/conformance/Overview.md new file mode 100644 index 000000000..7f2089c38 --- /dev/null +++ b/docs/api/conformance/Overview.md @@ -0,0 +1,35 @@ +--- +id: Conformance-Overview +sidebar_label: Overview +title: FDC3 Conformance Tests +hide_title: true +--- + +# FDC3 Conformance Tests + +This section contains test definitions that are used to test for conformance of a Desktop Agent API implementation with FDC3. + +:::warning + +Additions to the conformance tests for functionality introduced in FDC3 2.2 are still to be defined. + +Further, as FDC3 2.1 does not introduce changes to the Desktop Agent API, the conformance test set for FDC3 2.0 remains current at this time. Please see the [FDC3 2.1 Changelog entry](https://github.com/finos/FDC3/blob/main/CHANGELOG.md#fdc3-standard-21---2023-09-13) for more details. + +::: + +You can find the implementation of these tests in the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework) project. + +There are currently 6 sections to the tests. + +- [Basic Tests](Basic-Tests.md) +- [Open Tests](Open-Tests.md) +- [User Channel Tests](User-Channel-Tests.md) +- [App Channel Tests](App-Channel-Tests.md) +- [Metadata Tests](Metadata-Tests.md) +- [Intents Tests](Intents-Tests.md) + +:::info + +Where tests were introduced in this version of FDC3, they are labelled with an in the header, like so: ![2.2](https://img.shields.io/badge/FDC3-2.2-purple) + +::: diff --git a/docs/api/conformance/User-Channel-Tests.md b/docs/api/conformance/User-Channel-Tests.md new file mode 100644 index 000000000..2ad286908 --- /dev/null +++ b/docs/api/conformance/User-Channel-Tests.md @@ -0,0 +1,56 @@ +--- +id: User-Channel-Tests +sidebar_label: User Channel Tests +title: User Channel Tests +hide_title: true +--- + +# User Channel Tests + + +## Basic Broadcast + +| App | Step |Details | +|-----|--------------------|----------------------------------------------------------------------------------| +| A | 1.addContextListener |A adds an _unfiltered_ Context Listener using `addContextListener(null, handler)`.
A promise resolving to a `Listener` object is returned
Check that this has an `unsubscribe` method. | +| A | 2.joinUserChannel |A joins the first available (non-global) user channel. The available Channels are retrieved with:
`fdc3.getUserChannels()`
The first channel (that does not have the id 'global') is joined with:
`fdc3.joinUserChannel()` | +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | B broadcasts an `fdc3.instrument` context to the channel using `fdc3.broadcast()`.
Check a `void` promise is returned. | +| A | 5.Receive Context | A receives the instrument object, matching the one broadcast by B. | + +- `UCBasicUsage1` Perform above test. +- `UCBasicUsage2` Perform steps in order: 2,1,3,4,5 to confirm that the order of `joinUserChannel` and `addContextListener` calls doesn't matter. +- `UCBasicUsage3` Perform steps in order: 3,4,1,2,5 to confirm that the current context is automatically received on joining a channel. +- `UCBasicUsage4` Perform steps in order: 3,4,2,1,5 to confirm that the current context is automatically received on adding a context listener to an already joined a channel. + +## Filtered Broadcast + +| App | Step |Details | +|-----|--------------------|----------------------------------------------------------------------------------| +| A | 1.addContextListener |A adds a `fdc3.instrument` _typed_ Context Listener using `addContextListener("fdc3.instrument", handler)`.
A promise resolving a `Listener` object is returned
Check that this has an `unsubscribe` function.| +| A | 2.joinUserChannel |A joins the first available user channel using:
`getUserChannels()` Check **user** channels are returned.
Call `fdc3.joinChannel()` on the first non-global channel.| +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | B broadcasts:
1.`fdc3.broadcast()`.
2. `fdc3.broadcast()`
Check a `void` promise is returned. | +| A | 5.Receive Context | A receives the `fdc3.instrument` object, matching the one broadcast by B.
Check that the `fdc3.contact` is NOT received. | + +- `UCFilteredUsage1` Perform above test. +- `UCFilteredUsage2` Perform steps in order: 2,1,3,4,5. +- `UCFilteredUsage3` Perform steps in order: 3,4,1,2,5. +- `UCFilteredUsage4` Perform steps in order: 3,4,2,1,5. + +## Broadcast With Multiple Listeners + +| App | Step | Details | +|-----|--------------------|-------------------------------------------------------------------------------------------------------------| +| A | 1.addContextListeners | A sets up two Context Listeners. One for `fdc3.instrument` and one for `fdc3.contact` by calling: `addContextListener ("fdc3.instrument", handler)`
`addContextListener ("fdc3.contact", handler)`
A promise resolving a `Listener` object is returned for each.
Check that this has an `unsubscribe` method for each. | +| A | 2.joinUserChannel |A joins the first available user channel using:
`getUserChannels()` Check **user** channels are returned.
Call `fdc3.joinChannel()` on the first non-global channel.| +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | `fdc3.broadcast()`
`fdc3.broadcast()` . | +| A | 5.Receive Context | A's `fdc3.instrument` object matches the one broadcast by B, and arrives on the correct listener.
A's `fdc3.contact` object matches the one broadcast by B, and arrives on the correct listener. | + +- `UCFilteredUsage5`: Perform above test. +- `UCFilteredUsage6`: Perform above test, except B will join a _different_ channel to A. Check that you _don't_ receive anything. +- `UCFilteredUsageChange`: Perform above test, except that after joining, **A** changes channel to a _different_ channel via a further call to `fdc3.joinUserChannel`. Check that **A** does NOT receive anything. +- `UCFilteredUsageUnsubscribe`: Perform above test, except that after joining, **A** then `unsubscribe()`s from the channel using the `listener.unsubscribe` function. Check that **A** does NOT receive anything. +- `UCFilteredUsageLeave`: Perform above test, except that immediately after joining, **A** _leaves the channel_, and so receives nothing. +- `UCFilteredUsageNoJoin`: Perform the above test, but skip step 2 so that **A** does NOT join a channel. Confirm that the _current channel_ for **A** is NOT set before continuing with the rest of the test. **A** should receive nothing. diff --git a/docs/context/ref/Action.md b/docs/context/ref/Action.md index 85800d3cf..8e26904aa 100644 --- a/docs/context/ref/Action.md +++ b/docs/context/ref/Action.md @@ -8,7 +8,10 @@ sidebar_label: Action A representation of an FDC3 Action (specified via a Context or Context & Intent) that can be inserted inside another object, for example a chat message. -The action may be completed by calling `fdc3.raiseIntent()` with the specified Intent and Context, or, if only a context is specified, by calling `fdc3.raiseIntentForContext()` (which the Desktop Agent will resolve by presenting the user with a list of available Intents for the Context). +The action may be completed by calling: +- `fdc3.raiseIntent()` with the specified Intent and Context +- `fdc3.raiseIntentForContext()` if only a context is specified, (which the Desktop Agent will resolve by presenting the user with a list of available Intents for the Context). +- `channel.broadcast()` with the specified Context, if the `broadcast` action has been defined. Accepts an optional `app` parameter in order to specify a specific app. @@ -22,6 +25,19 @@ Accepts an optional `app` parameter in order to specify a specific app. ## Properties +
+ action + +**type**: `string` with values: +- `broadcast`, +- `raiseIntent` + +The **action** field indicates the type of action: +- **raiseIntent** : If no action or `raiseIntent` is specified, then `fdc3.raiseIntent` or `fdc3.raiseIntentForContext` will be called with the specified context (and intent if given). +- **broadcast** : If `broadcast` and a `channelId` are specified then `fdc3.getOrCreateChannel(channelId)` is called to retrieve the channel and broadcast the context to it with `channel.broadcast(context)`. If no `channelId` has been specified, the context should be broadcast to the current channel (`fdc3.broadcast()`) + +
+
title (required) @@ -50,20 +66,30 @@ A context object with which the action will be performed
+
+ channelId + +**type**: `string` + +Optional channel on which to broadcast the context. The `channelId` property is ignored unless the `action` is broadcast. + +
+
app **type**: api/AppIdentifier -An optional target application identifier that should perform the action +An optional target application identifier that should perform the action. The `app` property is ignored unless the action is raiseIntent.
-## Example +## Examples ```json { "type": "fdc3.action", + "action": "raiseIntent", "title": "Click to view Chart", "intent": "ViewChart", "context": { @@ -90,3 +116,29 @@ An optional target application identifier that should perform the action } ``` +```json +{ + "type": "fdc3.action", + "action": "broadcast", + "channelId": "Channel 1", + "title": "Click to view Chart", + "context": { + "type": "fdc3.chart", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "EURUSD" + } + } + ], + "range": { + "type": "fdc3.dateRange", + "starttime": "2020-09-01T08:00:00.000Z", + "endtime": "2020-10-31T08:00:00.000Z" + }, + "style": "candle" + } +} +``` + diff --git a/docs/context/ref/FileAttachment.md b/docs/context/ref/FileAttachment.md new file mode 100644 index 000000000..1226218e9 --- /dev/null +++ b/docs/context/ref/FileAttachment.md @@ -0,0 +1,59 @@ +--- +title: File Attachment +sidebar_label: File Attachment + +--- + +# File Attachment + +A File attachment encoded in the form of a data URI. Can be added to a Message. + +## Schema + + ([github](https://github.com/finos/FDC3/tree/main/schemas/context/fileAttachment.schema.json)) + +## Type + +`fdc3.fileAttachment` + +## Properties + +
+ data (required) + +**type**: `object` + +**Subproperties:** + +
+ name (required) + +**type**: `string` + +The name of the attached file + +
+ +
+ dataUri (required) + +**type**: `string` + +A data URI encoding the content of the file to be attached + +
+ +
+ +## Example + +```json +{ + "type": "fdc3.fileAttachment", + "data": { + "name": "myImage.png", + "dataUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" + } +} +``` + diff --git a/docs/context/ref/Message.md b/docs/context/ref/Message.md index 23c5c942c..41703b868 100644 --- a/docs/context/ref/Message.md +++ b/docs/context/ref/Message.md @@ -55,10 +55,10 @@ A map of string mime-type to string content
Additional Properties -**Any of:** +**One of:** - **type**: [Action](Action) -- **type**: `object` +- **type**: [File Attachment](FileAttachment)
diff --git a/docs/fdc3-compliance.md b/docs/fdc3-compliance.md index 64f242828..31798ef5a 100644 --- a/docs/fdc3-compliance.md +++ b/docs/fdc3-compliance.md @@ -70,6 +70,30 @@ FDC3 adopts the following experimental features policy: 5. Experimental features are exempted from the normal versioning and deprecation policies that govern changes to FDC3. I.e. breaking changes may be made to experimental features between versions of the Standard without a major version release. 6. The experimental designation may be removed from a feature in a minor version release (as this will be considered an additive change). +## Conformance testing + +The FDC3 Standards include a set of [definitions for conformance tests](api/conformance/Conformance-Overview) that may be used to determine if a Desktop Agent API implementation conforms to a particular Standard version, to help disambiguate complex parts of the FDC3 Standard and to enable test-driven development of a Desktop Agent implementation. + +The current set of tests focus on the Desktop Agent API and the interface to it. Tests are not yet defined for the App Directory API or Bridging API Parts of the FDC3 Standard, hence, conformance to those parts of the Standard must be determined manually. + +:::warning + +Additions to the conformance tests for functionality introduced in FDC3 2.2 are still to be defined. + +Further, as FDC3 2.1 does not introduce changes to the Desktop Agent API, the conformance test set for FDC3 2.0 remains current at this time. Please see the [FDC3 2.1 Changelog entry](https://github.com/finos/FDC3/blob/main/CHANGELOG.md#fdc3-standard-21---2023-09-13) for more details. + +::: + +The FDC3 Conformance tests are implemented for JavaScript/TypeScript web applications by the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework). Desktop Agent implementors working with web interfaces (Desktop Agent Preload or Desktop Agent Proxy) can clone the conformance framework and run the tests locally to determine if their agent is compliant with the Standard. + +Once a Desktop Agent has passed the conformance tests locally, its authors can [apply for a formal certification of compliance with the Standard from FINOS](https://github.com/finos/FDC3-conformance-framework/blob/main/instructions.md). Please note the [Terms and Conditions](https://github.com/finos/FDC3-conformance-framework/blob/main/terms-conditions/FDC3-Certified-Terms.md) of the Conformance Program. + +import badge_12 from '/img/community/certified-1.2.png'; +import badge_20 from '/img/community/certified-2.0.png'; + +Certified conformant with FDC3 1.2 badge +Certified conformant with FDC3 2.0 badge + ## Intellectual Property Claims Recipients of this document are requested to submit, with their comments, notification of diff --git a/toolbox/fdc3-conformance/README.md b/toolbox/fdc3-conformance/README.md deleted file mode 100644 index b27819b79..000000000 --- a/toolbox/fdc3-conformance/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# FDC3 Conformance Testing - -This folder contains test packs (test definitions) for conformance with FDC3. - -You can find the implementation of these tests in the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework) project. - -There are currently 5 sections to this. Where tests apply to a particular version of FDC3, this is labelled with icons in the header, like so: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) - -- [Basic Tests](Basic-Tests.md) -- [Open Tests](Open-Tests.md) -- [User Channel Tests](User-Channel-Tests.md) -- [App Channel Tests](App-Channel-Tests.md) -- [Metadata Tests](Metadata-Tests.md) -- [Intents Tests](Intents-Tests.md) - diff --git a/website/sidebars.json b/website/sidebars.json index 3b26ad7eb..36f84d390 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -33,6 +33,19 @@ "api/specs/webConnectionProtocol", "api/specs/desktopAgentCommunicationProtocol" ] + }, + { + "type": "category", + "label": "Conformance Tests", + "items": [ + "api/conformance/Conformance-Overview", + "api/conformance/Basic-Tests", + "api/conformance/App-Channel-Tests", + "api/conformance/User-Channel-Tests", + "api/conformance/Open-Tests", + "api/conformance/Intents-Tests", + "api/conformance/Metadata-Tests" + ] } ] }, @@ -86,6 +99,7 @@ "context/ref/Country", "context/ref/Currency", "context/ref/Email", + "context/ref/FileAttachment", "context/ref/Instrument", "context/ref/InstrumentList", "context/ref/Interaction", diff --git a/toolbox/fdc3-conformance/App-Channel-Tests.md b/website/versioned_docs/version-2.0/api/conformance/App-Channel-Tests.md similarity index 98% rename from toolbox/fdc3-conformance/App-Channel-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/App-Channel-Tests.md index 597d0bc28..cc361822c 100644 --- a/toolbox/fdc3-conformance/App-Channel-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/App-Channel-Tests.md @@ -1,3 +1,10 @@ +--- +id: App-Channel-Tests +sidebar_label: App Channel Tests +title: App Channel Tests +hide_title: true +--- + # App Channel Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) ## Basic Broadcast diff --git a/toolbox/fdc3-conformance/Basic-Tests.md b/website/versioned_docs/version-2.0/api/conformance/Basic-Tests.md similarity index 97% rename from toolbox/fdc3-conformance/Basic-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/Basic-Tests.md index 6442e05bc..24807aaea 100644 --- a/toolbox/fdc3-conformance/Basic-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/Basic-Tests.md @@ -1,3 +1,10 @@ +--- +id: Basic-Tests +sidebar_label: Basic Tests +title: Basic Tests +hide_title: true +--- + # Basic Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) _These are some basic sanity tests implemented in the FDC3 Conformance Framework. It is expected that Desktop Agent testers will run these first before commencing the much more thorough tests in section 2 onwards._ @@ -26,4 +33,3 @@ _These are some basic sanity tests implemented in the FDC3 Conformance Framework - `BasicRI2`: The application should be able to raise an intent for some item of context by invoking: - `fdc3.raiseIntentForContext()` - A promise should be returned. - diff --git a/toolbox/fdc3-conformance/Intents-Tests.md b/website/versioned_docs/version-2.0/api/conformance/Intents-Tests.md similarity index 96% rename from toolbox/fdc3-conformance/Intents-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/Intents-Tests.md index 6d331a5a3..ad7b69cfd 100644 --- a/toolbox/fdc3-conformance/Intents-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/Intents-Tests.md @@ -1,3 +1,10 @@ +--- +id: Intents-Tests +sidebar_label: Intents Tests +title: Intents Tests +hide_title: true +--- + # Intents Tests ## Version 1.2 Intents Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) @@ -35,8 +42,8 @@ Also we assume a fourth app **D** that is going to discover the intents in the o | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| -| D | 1. Raise | `fdc3.raiseIntent(‘sharedTestingIntent1’, {testContextY})`
starts app B. | -| B | 2. Gather Context | `fdc.addIntentListener(‘sharedTestingIntent1’)`
Receives testContextY, matching that sent by D | +| D | 1. Raise | `fdc3.raiseIntent(‘sharedTestingIntent1’, {testContextY})`
starts app B. | +| B | 2. Gather Context | `fdc.addIntentListener(‘sharedTestingIntent1’)`
Receives testContextY, matching that sent by D | - `SingleResolve1`: Perform above test - `TargetedResolve1`: Use `fdc3.raiseIntent(‘aTestingIntent’, {testContextX}, )` to start app A, otherwise, as above @@ -117,8 +124,8 @@ Finally, please note that this is a larger set of apps than were required for 1. | App | Step | Details | |-------|----------------|---------------------------------------------------------------------------------------------------| -| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | -| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent1")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent1")` to register its listener.
It then receives `testContextX`, matching that sent by Test | | Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set.** | - `2.0-RaiseIntentSingleResolve`: Perform above test @@ -163,8 +170,8 @@ Finally, please note that this is a larger set of apps than were required for 1. | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| -| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | -| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent")` to register its listener.
It then receives `testContextX`, matching that sent by Test | | Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set. | | Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | | A | 5. return void | A should return `void` after a short delay (e.g. 5 seconds). | @@ -178,8 +185,8 @@ Finally, please note that this is a larger set of apps than were required for 1. | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| -| Test | 1. Raise | `fdc3.raiseIntent("sharedTestingIntent1", testContextY)`
starts app **B**. | -| B | 2. Receive Intent & Context | After starting up, B runs `fdc3.addIntentListener("sharedTestingIntent1")` to register its listener.
It then receives `testContextY`, matching that sent by Test | +| Test | 1. Raise | `fdc3.raiseIntent("sharedTestingIntent1", testContextY)`
starts app **B**. | +| B | 2. Receive Intent & Context | After starting up, B runs `fdc3.addIntentListener("sharedTestingIntent1")` to register its listener.
It then receives `testContextY`, matching that sent by Test | | Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App B's `appId` and `instanceId` set. | | Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | | B | 5. return `testContextY` | B should return a `testContextY` instance after a short delay (e.g. 5 seconds). | @@ -193,8 +200,8 @@ Finally, please note that this is a larger set of apps than were required for 1. | App | Step | Details | |-------|-----------------------|---------------------------------------------------------------------------------------------------| -| Test | 1. Raise Intent | Test raises an intent with `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`
starts app E. | -| E | 2. Receive Intent & Context | After starting up, E runs `fdc3.addIntentListener("sharedTestingIntent2")` to register its listener.
It them receives `testContextY`, matching that sent by Test | +| Test | 1. Raise Intent | Test raises an intent with `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`
starts app E. | +| E | 2. Receive Intent & Context | After starting up, E runs `fdc3.addIntentListener("sharedTestingIntent2")` to register its listener.
It them receives `testContextY`, matching that sent by Test | | Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App E's `appId` and `instanceId` set. | | Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | | E | 5. return Channel | E should retrieve a Channel object via `fdc3.getOrCreateChannel("someChannelName")` and return it immediately. | @@ -227,11 +234,11 @@ Finally, please note that this is a larger set of apps than were required for 1. | App | Step | Details | |-------|-----------------|---------------------------------------------------------------------------------------------------| -| Test | 1. Raise intent | Test raises an intent with `fdc3.raiseIntent(‘"kTestingIntent", testContextX, {appId: ""})`
starts app K. | -| K | 2. Receive Intent & Context | After starting up, K runs `fdc3.addIntentListener("kTestingIntent")` to register its listener.
It them receives `testContextX`, matching that sent by Test | +| Test | 1. Raise intent | Test raises an intent with `fdc3.raiseIntent(‘"kTestingIntent", testContextX, {appId: ""})`
starts app K. | +| K | 2. Receive Intent & Context | After starting up, K runs `fdc3.addIntentListener("kTestingIntent")` to register its listener.
It them receives `testContextX`, matching that sent by Test | | Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App K's `appId` and `instanceId` set. | | Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | -| K | 5. Create PrivateChannel and setup event listeners | K should create a `PrivateChannel` object via `const privChan = await fdc3.createPrivateChannel()`,
it should then add listeners for the 3 events offered + a context listener via:
- `const listener1 = await privChan.onAddContextListener(handler1);`
- `const listener2 = await privChan.onUnsubscribe(handler2);`
- `const listener3 = await privChan.onDisconnect(handler3);`
- `const listener4 = await privChan.addContextListener("testContextX", handler4)`
it should then return the `PrivateChannel`. | +| K | 5. Create PrivateChannel and setup event listeners | K should create a `PrivateChannel` object via `const privChan = await fdc3.createPrivateChannel()`,
it should then add listeners for the 3 events offered + a context listener via:
- `const listener1 = await privChan.onAddContextListener(handler1);`
- `const listener2 = await privChan.onUnsubscribe(handler2);`
- `const listener3 = await privChan.onDisconnect(handler3);`
- `const listener4 = await privChan.addContextListener("testContextX", handler4)`
it should then return the `PrivateChannel`. | | Test | 6. receive PrivateChannel | The promise received by Test from `resolution.getResult()` should resolve to a `PrivateChannel` object. Confirm that the `type` of the Channel object is "private". | Test | 7. addContextListener | Test should add a context listener to the PrivateChannel object via `const listener1 = privChan.addContextListener("testContextZ", handler)` | | K | 8. Receive event & broadcast context | The `onAddContextListener` handler (`listener1`) added in step 5 should fire after Test adds its listener. Once it has, K should broadcast a short stream of `testContextZ` objects, with consecutive integer values in them (e.g. 1-5). | diff --git a/toolbox/fdc3-conformance/Metadata-Tests.md b/website/versioned_docs/version-2.0/api/conformance/Metadata-Tests.md similarity index 87% rename from toolbox/fdc3-conformance/Metadata-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/Metadata-Tests.md index 8fdcaf2f7..456b4bff3 100644 --- a/toolbox/fdc3-conformance/Metadata-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/Metadata-Tests.md @@ -1,11 +1,17 @@ +--- +id: Metadata-Tests +sidebar_label: Metadata Tests +title: Metadata Tests +hide_title: true +--- -# Metadata & Instance Test Cases +# Metadata & Instance Test Cases You will need to pre-populate the AppDirectory with the following items: | App | Required Metadata | |-----|------------------------------------------| -| A | Generic AppD Record which contains at least the following fields:
- `name`
- `version`
- `title`
- `tooltip`
- `description`
- `icons` (`Array`)
- `screenshots` (`Array`)
- `interop.intents.listensFor` (`aTestingIntent` with at least context type `testContextX`) | +| A | Generic AppD Record which contains at least the following fields:
- `name`
- `version`
- `title`
- `tooltip`
- `description`
- `icons` (`Array`)
- `screenshots` (`Array`)
- `interop.intents.listensFor` (`aTestingIntent` with at least context type `testContextX`) | ## Retrieve `AppMetadata` ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) @@ -21,7 +27,7 @@ You will need to pre-populate the AppDirectory with the following items: | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| | Test | 1.Open1 | Open a first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | -| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | | Test | 3.getAppMetadata1 | Retrieve metadata for the first instance of the app with
`const metadata1 = fdc3.getAppMetadata(appIdentifier1)` | | Test | 4.Confirm1 | Compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. | | Test | 5.getAppMetadata2 | Retrieve metadata for the second instance of the app with
`const metadata2 = fdc3.getAppMetadata(appIdentifier2)` | @@ -34,7 +40,7 @@ You will need to pre-populate the AppDirectory with the following items: | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| | Test | 1.Open1 | Open the first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | -| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | | Test | 3.FindInstances | Retrieve details of open instances of app A with
`let instances = await fdc3.findInstances({appId: ""})`
confirm that both `appIdentifier1` and `appIdentifier2` are both present in the array. | | Test | 4.RaiseIntent | Use `appIdentifier1` to raise an intent and target that instance, with
`const resolution = fdc3.raiseIntent("aTestingIntent", {"type": "testContextX"}, appIdentifier1)` | | Test | 5.Confirm1 | Check that `resolution.source` matches `appIdentifier1` | @@ -47,7 +53,7 @@ You will need to pre-populate the AppDirectory with the following items: | App | Step | Details | |-----|----------------|---------------------------------------------------------------------------------------------------| | Test | 1.getInfo |Retrieve the `ImplementationMetadata` for the DesktopAgent with
- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `let implMetadata = fdc3.getInfo()`
- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.getInfo().then((implMetadata) => { subsequent steps }`
**Note that the use of `then` is deliberate and intended to confirm that a promise returned (as this function switched from synchronous to asynchronous in 2.0)**| -| Test | 2.CheckVersion | Check that the `fdc3Version` variable is present and at or greater than:
- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) 1.2
- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) 2.0
(which you can do with the [`versionIsAtLeast` function from FDC3's Methods.ts](https://github.com/finos/FDC3/blob/add64f8302c6dcdc8437cf0e245101e927b69ec2/src/api/Methods.ts#L207):
`const isFDC3v2 = versionIsAtLeast(implMetadata, "2.0")` | +| Test | 2.CheckVersion | Check that the `fdc3Version` variable is present and at or greater than:
- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) 1.2
- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) 2.0
(which you can do with the [`versionIsAtLeast` function from FDC3's Methods.ts](https://github.com/finos/FDC3/blob/add64f8302c6dcdc8437cf0e245101e927b69ec2/src/api/Methods.ts#L207):
`const isFDC3v2 = versionIsAtLeast(implMetadata, "2.0")` | | Test | 3.CheckProvider | Check that the `provider` variable is present and not an empty string | | Test | 4.CheckFeatures | ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Check that the `optionalFeatures`, `optionalFeatures.OriginatingAppMetadata` and `optionalFeatures.UserChannelMembershipAPIs` variables are all present and that the latter two provide boolean values | diff --git a/toolbox/fdc3-conformance/Open-Tests.md b/website/versioned_docs/version-2.0/api/conformance/Open-Tests.md similarity index 98% rename from toolbox/fdc3-conformance/Open-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/Open-Tests.md index 2e504d3e5..85e8f0aa6 100644 --- a/toolbox/fdc3-conformance/Open-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/Open-Tests.md @@ -1,4 +1,11 @@ -# Open Tests +--- +id: Open-Tests +sidebar_label: Open Tests +title: Open Tests +hide_title: true +--- + +# Open Tests ## A Opens B diff --git a/website/versioned_docs/version-2.0/api/conformance/Overview.md b/website/versioned_docs/version-2.0/api/conformance/Overview.md new file mode 100644 index 000000000..1cd4d9df1 --- /dev/null +++ b/website/versioned_docs/version-2.0/api/conformance/Overview.md @@ -0,0 +1,21 @@ +--- +id: Conformance-Overview +sidebar_label: Overview +title: FDC3 Conformance Tests +hide_title: true +--- + +# FDC3 Conformance Tests + +This section contains test definitions that are used to test for conformance of a Desktop Agent API implementation with FDC3. + +You can find the implementation of these tests in the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework) project. + +There are currently 6 sections to the tests. Where tests apply to a particular version of FDC3, this is labelled with icons in the header, like so: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +- [Basic Tests](Basic-Tests.md) +- [Open Tests](Open-Tests.md) +- [User Channel Tests](User-Channel-Tests.md) +- [App Channel Tests](App-Channel-Tests.md) +- [Metadata Tests](Metadata-Tests.md) +- [Intents Tests](Intents-Tests.md) diff --git a/toolbox/fdc3-conformance/User-Channel-Tests.md b/website/versioned_docs/version-2.0/api/conformance/User-Channel-Tests.md similarity index 96% rename from toolbox/fdc3-conformance/User-Channel-Tests.md rename to website/versioned_docs/version-2.0/api/conformance/User-Channel-Tests.md index 923a5332c..89f1b03cf 100644 --- a/toolbox/fdc3-conformance/User-Channel-Tests.md +++ b/website/versioned_docs/version-2.0/api/conformance/User-Channel-Tests.md @@ -1,8 +1,14 @@ +--- +id: User-Channel-Tests +sidebar_label: User Channel Tests +title: User Channel Tests +hide_title: true +--- + # User Channel Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) _NB: User Channels were called System Channels in FDC3 1.2. The new terminology is used in this specification_ - ## Basic Broadcast | App | Step |Details | @@ -41,7 +47,7 @@ _NB: User Channels were called System Channels in FDC3 1.2. The new terminolog | A | 2.joinUserChannel |A joins the first available user channel using:
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `getSystemChannels()` Check channels are returned.
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `getUserChannels()` Check **user** channels are returned.
Call `fdc3.joinChannel()` on the first non-global channel.| | B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | | B | 4.Broadcast | `fdc3.broadcast()`
`fdc3.broadcast()` . | -| A | 5.Receive Context | A's `fdc3.instrument` object matches the one broadcast by B, and arrives on the correct listener.
A's `fdc3.contact` object matches the one broadcast by B, and arrives on the correct listener. | +| A | 5.Receive Context | A's `fdc3.instrument` object matches the one broadcast by B, and arrives on the correct listener.
A's `fdc3.contact` object matches the one broadcast by B, and arrives on the correct listener. | - `UCFilteredUsage5`: Perform above test - `UCFilteredUsage6`: Perform above test, except B will join a _different_ channel to A. Check that you _don't_ receive anything. diff --git a/website/versioned_docs/version-2.0/fdc3-compliance.md b/website/versioned_docs/version-2.0/fdc3-compliance.md index 225b066e3..7dc3cc82d 100644 --- a/website/versioned_docs/version-2.0/fdc3-compliance.md +++ b/website/versioned_docs/version-2.0/fdc3-compliance.md @@ -70,6 +70,22 @@ FDC3 adopts the following experimental features policy: 5. Experimental features are exempted from the normal versioning and deprecation policies that govern changes to FDC3. I.e. breaking changes may be made to experimental features between versions of the Standard without a major version release. 6. The experimental designation may be removed from a feature in a minor version release (as this will be considered an additive change). +## Conformance testing + +The FDC3 Standards include a set of [definitions for conformance tests](api/conformance/Conformance-Overview) that may be used to determine if a Desktop Agent API implementation conforms to a particular Standard version, to help disambiguate complex parts of the FDC3 Standard and to enable test-driven development of a Desktop Agent implementation. + +The current set of tests focus on the Desktop Agent API and the interface to it. Tests are not yet defined for the App Directory API or Bridging API Parts of the FDC3 Standard, hence, conformance to those parts of the Standard must be determined manually. + +The FDC3 Conformance tests are implemented for JavaScript/TypeScript web applications by the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework). Desktop Agent implementors working with web interfaces (Desktop Agent Preload or Desktop Agent Proxy) can clone the conformance framework and run the tests locally to determine if their agent is compliant with the Standard. + +Once a Desktop Agent has passed the conformance tests locally, its authors can [apply for a formal certification of compliance with the Standard from FINOS](https://github.com/finos/FDC3-conformance-framework/blob/main/instructions.md). Please note the [Terms and Conditions](https://github.com/finos/FDC3-conformance-framework/blob/main/terms-conditions/FDC3-Certified-Terms.md) of the Conformance Program. + +import badge_12 from '/img/community/certified-1.2.png'; +import badge_20 from '/img/community/certified-2.0.png'; + +Certified conformant with FDC3 1.2 badge +Certified conformant with FDC3 2.0 badge + ## Intellectual Property Claims Recipients of this document are requested to submit, with their comments, notification of diff --git a/website/versioned_docs/version-2.1/api/conformance/App-Channel-Tests.md b/website/versioned_docs/version-2.1/api/conformance/App-Channel-Tests.md new file mode 100644 index 000000000..cc361822c --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/App-Channel-Tests.md @@ -0,0 +1,61 @@ +--- +id: App-Channel-Tests +sidebar_label: App Channel Tests +title: App Channel Tests +hide_title: true +--- + +# App Channel Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +## Basic Broadcast + +| App | Step | Details | +|-----|--------------------|----------------------------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| A | 2.Add Context Listener |Add an _untyped_ context listener to the channel, using:
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `await testChannel.addContextListener(null, handler)`
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `testChannel.addContextListener(null, handler)` | +| B | 3.Retrieve `Channel` | Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 4.Broadcast | Broadcast an `fdc3.instrument` Context to the channel with:
`testChannel.broadcast()`| +| A | 5.Receive Context | The handler added in step 2 will receive the instrument context. Ensure that the instrument received by A is identical to that sent by B. | + +- `ACBasicUsage1` Perform above test. + +## Current Context + +| App | Step | Details | +|-----|--------------------|----------------------------------------------------------------------------| +| B | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| B | 2.Broadcast | Broadcast an `fdc3.instrument` to the channel using:
`testChannel.broadcast()`| +| A | 3.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel B did (`test-channel`)| +| A | 4.Retrieve Current Context | A gets the _current context_ of the user channel. via: `await testChannel.getCurrentContext()`
Ensure that the instrument received by A is identical to that sent by B | + +- `ACBasicUsage2` Perform above test + +## Filtered Context + +| App | Step | Details | +|-----|--------------------|-----------------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| A | 2.Add Context Listener |Add an _typed_ context listener for `fdc3.instrument`, using:
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `await testChannel.addContextListener("fdc3.instrument", handler)`
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `testChannel.addContextListener("fdc3.instrument", handler)` +| B | 3.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 4.Broadcast | B broadcasts both an `fdc3.instrument` context and an `fdc3.contact` context, using:
`testChannel.broadcast()`
`testChannel.broadcast()`| +| A | 5.Receive Context | An fdc3.instrument context is received by the handler added in step 2.
Ensure that the fdc3.instrument received by A is identical to that sent by B
Ensure that the fdc3.contact context is NOT received. | + +- `ACFilteredContext1`: Perform above test +- `ACFilteredContext2`: Perform above test, but add listeners for both `fdc3.instrument` and `fdc3.contact` in step2. Ensure that both context objects are received. +- `ACFilteredContext3`: Perform above test, except creating a _different_ channel in app B. Check that you _don't_ receive anything (as the channels don't match). +- `ACFilteredContext4`: Perform above test, except that after creating the channel **A** creates another channel with a further _different_ channel id and adds a further context listener to it. Ensure that **A** is still able to receive context on the first channel (i.e. it is unaffected by the additional channel) and does NOT receive anything on the second channel. +- `ACUnsubscribe`: Perform above test, except that after creating the channel **A** then `unsubscribe()`s the listener it added to the channel. Check that **A** does NOT receive anything. + +### App Channel History + +| App | Step | Details | +|-----|--------------------|---------------------------------------------------------| +| A | 1.Retrieve `Channel` |Retrieve a `Channel` object representing an 'App' channel called `test-channel` using:
`const testChannel = await fdc3.getOrCreateChannel("test-channel")` | +| B | 2.Retrieve `Channel` |Retrieve a `Channel` object representing the same 'App' channel A did (`test-channel`)| +| B | 3.Broadcast |B broadcasts both the instrument context and a contact context, using:
`testChannel.broadcast()`
`testChannel.broadcast()` | +| A | 4.Add Context Listener| A adds a context listener to the channel *after* B has completed all its broadcasts, via:
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `await testChannel.addContextListener("fdc3.instrument", handler)`
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `testChannel.addContextListener("fdc3.instrument", handler)`
Ensure that A does NOT receive any context via these listeners (past context is only retrieved via a `getCurrentContext()` call on App channels). | +| A | 5.Retrieve Current Context | A is able to retrieve the most recent context of each context type from the `Channel` via:
`const instrument = await testChannel.getCurrentContext('fdc3.instrument')`
`const instrument = await testChannel.getCurrentContext('fdc3.contact')`
Ensure that both contexts retreived by A are identical to those sent by B| + +- `ACContextHistoryTyped`: Perform above test. +- `ACContextHistoryMultiple`: **B** Broadcasts multiple history items of both types. Ensure that only the last version of each type is received by **A**. +- `ACContextHistoryLast`: In step 5. **A** retrieves the _untyped_ current context of the channel via `const currentContext = await testChannel.getCurrentContext()`. Ensure that A receives only the very last broadcast context item _of any type_. diff --git a/website/versioned_docs/version-2.1/api/conformance/Basic-Tests.md b/website/versioned_docs/version-2.1/api/conformance/Basic-Tests.md new file mode 100644 index 000000000..24807aaea --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/Basic-Tests.md @@ -0,0 +1,35 @@ +--- +id: Basic-Tests +sidebar_label: Basic Tests +title: Basic Tests +hide_title: true +--- + +# Basic Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +_These are some basic sanity tests implemented in the FDC3 Conformance Framework. It is expected that Desktop Agent testers will run these first before commencing the much more thorough tests in section 2 onwards._ + +- `BasicCL1`: You can create a context listener by calling `fdc3.addContextListener('fdc3.contact',)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicCL2`: You can create an **unfiltered** context listener by calling `fdc3.addContextListener(null,)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicIL1`: You can create an intent listener by calling `fdc3.addIntentListener(,)`. A `Listener` object is returned and can be used to remove the listener again by calling its `unsubscribe` function. +- `BasicGI1`: An application can retrieve an `ImplementationMetadata` object to find out the version of FDC3 it is using and the provider details by calling: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.getInfo()` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `await fdc3.getInfo()` +- `BasicAC1`: An application can retrieve a named 'App' channel via the `fdc3.getOrCreateChannel()` function. The `Channel` object returned conforms to the defined interface. +- `BasicUC1`: An application can query the available user/system channels, which are returned as an array of `Channel` Objects conforming to the defined interface. The API call is: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.getSystemChannels()` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue)`await fdc3.getUserChannels()` +- `BasicJC1`: The application should be able to join one of the user/system channels with the channel's id. Having done so, the current channel should NOT be null, and be set for the application _to the channel for the id given_. After you leave the current channel, it should go back to being `null`. + - The channel is joined with: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.joinChannel()` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.joinUserChannel()` + - A `Channel` object representing the current channel is retrieved with: + - `fdc3.getCurrentChannel()` to get the current channel. + - The channel is left with: + - `fdc3.leaveCurrentChannel()` +- `BasicRI1`: The application should be able to raise an intent by invoking: + - `fdc3.raiseIntent()` + - A promise should be returned. +- `BasicRI2`: The application should be able to raise an intent for some item of context by invoking: + - `fdc3.raiseIntentForContext()` + - A promise should be returned. diff --git a/website/versioned_docs/version-2.1/api/conformance/Intents-Tests.md b/website/versioned_docs/version-2.1/api/conformance/Intents-Tests.md new file mode 100644 index 000000000..ad7b69cfd --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/Intents-Tests.md @@ -0,0 +1,290 @@ +--- +id: Intents-Tests +sidebar_label: Intents Tests +title: Intents Tests +hide_title: true +--- + +# Intents Tests + +## Version 1.2 Intents Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) + +_NB: Intents received a lot of work in 2.0 version of the specification, so by and large the tests for these are now completely different._ + +### Setup + +You will need to pre-populate the AppDirectory with the following items: + +| App | Required Metadata | +|-----|------------------------------------------------------------------------------------------------------------------------------------------------------| +| A | A’s AppD Record contains: `aTestingIntent` (with context type `testContextX`, `testContextZ`) and `sharedTestingIntent1` (with context type `testContextX`) | +| B | B’s AppD Record contains `bTestingIntent` (with context type `testContextY`) and `sharedTestingIntent1` (with context types `testContextX` and `testContextY`) | +| C | C’s AppD Record contains `cTestingIntent` (with context type `testContextX`) | + +Also we assume a fourth app **D** that is going to discover the intents in the other 3. + +### Find Intent From AppD + +- `IntentAppD`: Calls `fdc3.findIntent(‘aTestingIntent’)`. Receives promise containing an appIntent with metadata containing `aTestingIntent` and only **A** app metadata. +- `WrongIntentAppD`: Calls `fdc3.findIntent(‘nonExistentIntent’)`. Rejects with no apps found error https://fdc3.finos.org/docs/api/ref/Errors#resolveerror +- `IntentAppDRightContext`: Calls `fdc3.findIntent(‘aTestingIntent’, ‘testContextX’)`. Receives promise containing an appIntent with metadata containing `aTestingIntent` and only **A** app metadata. +- `IntentAppDWrongContext`: Calls `fdc3.findIntent(‘aTestingIntent’, ‘testContextY’)`. Rejects with no apps found error https://fdc3.finos.org/docs/api/ref/Errors#resolveerror +- `IntentAppDMultiple1`: Calls `fdc3.findIntent(‘sharedTestingIntent1’)`. Receives promise containing an appIntent with metadata containing `sharedTestingIntent` and only **A** and **B** app metadata. +- `IntentAppDMultiple2`: Calls `fdc3.findIntent(‘sharedTestingIntent1’, 'testContextX')`. Receives promise containing an appIntent with metadata containing `sharedTestingIntent` and only **A** and **B** app metadata. +- `IntentAppDMultiple3`: Calls `fdc3.findIntent(‘sharedTestingIntent1’, 'testContextY')`. Receives promise containing an appIntent with metadata containing `sharedTestingIntent` and only **B** app metadata. + +### Find Intents By Context + +- `SingleContext`: Call `fdc3.findIntentsByContext(‘testContextX’)`. Should return `aTestingIntent` (app **A**), `sharedTestingIntent1` (**A**, **B**) and `cTestingIntent` (**C**) AND nothing else. +- `NoContext`: Call `fdc3.findIntentsByContext()`. Throws error of `NoAppsFound` + +### Raise Intent + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| D | 1. Raise | `fdc3.raiseIntent(‘sharedTestingIntent1’, {testContextY})`
starts app B. | +| B | 2. Gather Context | `fdc.addIntentListener(‘sharedTestingIntent1’)`
Receives testContextY, matching that sent by D | + +- `SingleResolve1`: Perform above test +- `TargetedResolve1`: Use `fdc3.raiseIntent(‘aTestingIntent’, {testContextX}, )` to start app A, otherwise, as above +- `TargetedResolve2`: Use `fdc3.raiseIntent(‘aTestingIntent’, {testContextX}, {name: ""})` to start app A, otherwise, as above +- `TargetedResolve3`: Use `fdc3.raiseIntent(‘aTestingIntent’, {testContextX}, {name: “”, appId: “”})` to start app B, otherwise, as above +- `FailedResolve1-3` As with `TargetedResolve1-3`, but use `fdc3.raiseIntent(‘aTestingIntent’, {testContextY}, )` and variations. You will receive `NoAppsFound` Error +- `FailedResolve4` As above, but use `fdc3.raiseIntent(‘aTestingIntent’, {testContextX}, )`. You will receive `NoAppsFound` Error + +## Current Intents Tests ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +_Please note that API calls (and associated test cases) relating to API calls based on the `name` property of an appD record (used to specify a target application) were deprecated in FDC3 2.0 in favour of those based on `AppIdentifier`. Hence, those API calls have become optional and test cases related to them have been removed._ + +### Setup + +We assume 6 context types in the below tests (and associated AppD records): + +- `testContextX` +- `testContextY` +- `testContextZ` +- `nonExistentContext` (context object with a unique type that does NOT appear in any of the apps (metadata or otherwise). +- `privateChannelDetails` +- `privateChannelIsPrivateResult` + +These may be used in a test as a context object `{ "type": "" }` or just the base type name. Where the base type name is used it is surround with "quotes". If not wrapped in quotes assume it is an instance of that context type (generally just an object with a `type` field set to the type name - but occasionally with other data). + +You will need to pre-populate the AppDirectory with the following items (some of which will never be started, but must be configured to confirm correct behavior from various API functions): + +| App | Usage | ListensFor `(pattern: intent([context-types…]) (=> result-type)`) | On Startup | +|-----|-------------------------------------------------------|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| A | Raise Intent tests without results | `aTestingIntent(testContextX,testContextZ)`
`sharedTestingIntent1(testContextX)` | addIntentListener() for given intents | +| B | Raise Intent tests with Context results | `bTestingIntent(testContextY)`
`sharedTestingIntent1(testContextX, testContextY) => testContextY` | addIntentListener() for given intents | +| C | Find Intent tests (never started) | `cTestingIntent(testContextX) => testContextZ` | addIntentListener() for given intents | +| D | Find Intent tests (never started) | `sharedTestingIntent2(testContextX) => testContextZ` | addIntentListener() for given intents | +| E | Find Intent & Raise Intent with Channel result | `sharedTestingIntent2(testContextY) => channel` | addIntentListener() for given intents | +| F | Find Intent & Raise Intent with PrivateChannel result | `sharedTestingIntent2(testContextY) => channel` * | addIntentListener() for given intents | +| G | Find Intent tests (never started) | `sharedTestingIntent2(testContextY)` | addIntentListener() for given intents | +| H | Raise Intent (bad config/behavior) | `sharedTestingIntent2(testContextY) => testContextZ` | - no action | +| I | Raise Intent (bad config/behavior) | `sharedTestingIntent2(testContextY) => testContextZ` | addIntentListener(‘MadeUpIntent’, handler) | +| J | PrivateChannels are private | `privateChannelIsPrivate(privateChannelDetails) => privateChannelIsPrivateResult` | Tries to retrieve privateChannel sent in the privateChannelDetails context, fails | +| K | PrivateChannel lifecycle events | `kTestingIntent(testContextX) => channel` | addIntentListener() for given intents | + +NB: + +- There is no way to indicate in the app directory the difference between a private channel and app channel. +- We assume a final test app `Test` that will discover the Intent support in the others using the API. + +Finally, please note that this is a larger set of apps than were required for 1.2 tests. This is due to an increased number of parameters to API calls and AppD records, which multiplies the number of apps required. The apps are all specified here (rather than broken down over multiple issues) to ensure that clashes between test case sets can be worked out here. For example, adding one additional app that works with a particular intent/context pair might corrupt the results of multiple `findIntent` or `raiseIntent` tests. Hence, please stick to the defined type and report any issues you find so that they can be rectified in these definitions. + +### Find Intent basic usage + +- `2.0-FindIntentAppD`: Calls `fdc3.findIntent("aTestingIntent")`. Receives promise containing an appIntent with metadata containing `aTestingIntent` and only **A** `AppMetadata`. +- `2.0-FindNonExistentIntentAppD`: Calls `fdc3.findIntent("nonExistentIntent")`. Rejects with an Error whose `message` is `ResolveError.NoAppsFound` https://fdc3.finos.org/docs/api/ref/Errors#resolveerror +- `2.0-FindIntentAppDRightContext`: Calls `fdc3.findIntent("aTestingIntent", "testContextX")`. Receives promise containing an `AppIntent` with metadata containing `aTestingIntent` and only metadata for app **A**. +- `2.0-FindIntentAppDWrongContext`: Calls `fdc3.findIntent("aTestingIntent", "testContextY")`. Rejects with an Error whose `message` is `ResolveError.NoAppsFound` https://fdc3.finos.org/docs/api/ref/Errors#resolveerror +- `2.0-FindIntentAppDMultiple1`: Calls `fdc3.findIntent("sharedTestingIntent2")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and metadata for apps **D**, **E**, **F**, **G**, **H** and **I** only. +- `2.0-FindIntentAppDMultiple2`: Calls `fdc3.findIntent("sharedTestingIntent2", "testContextY")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and `AppMetadata` for apps **E**, **F**, **G**, **H** and **I** only. + +### Find Intents By Context + +- `2.0-FindIntentByContextSingleContext`: Call `fdc3.findIntentsByContext(testContextX)`. Should return: + - `aTestingIntent` (app **A**), + - `sharedTestingIntent1` (**A**, **B**) + - `cTestingIntent` (**C**), + - `sharedTestingIntent2` (**D**) + - `kTestingIntent` (**K**), + - AND nothing else. +- `2.0FindIntentByContextWrongIntentAppD`: Calls `fdc3.findIntentsByContext(nonExistentContext)`. Rejects with an Error whose `message` is `ResolveError.NoAppsFound` https://fdc3.finos.org/docs/api/ref/Errors#resolveerror + +### Find Intents By Result Type + +- `2.0-FindIntentAppDByResultSingle`: Calls `fdc3.findIntent("cTestingIntent", testContextX, "testContextZ")`. Receives promise containing an `AppIntent` with metadata containing `cTestingIntent` and only **C** app metadata. +- `2.0-FindIntentAppDByResultSingleNullContext`: Calls `fdc3.findIntent("cTestingIntent", null, "testContextZ")`. Receives promise containing an `AppIntent` with metadata containing `cTestingIntent` and only **C** app metadata. +- `2.0-FindIntentAppDByResultMultiple`: Calls `fdc3.findIntent("sharedTestingIntent1", testContextX, "testContextY")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent1` and only **B** app metadata. +- `2.0-FindIntentAppDByResultChannel1`: Calls `fdc3.findIntent("sharedTestingIntent2", testContextY, "channel")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent2` and only **E** and **F** app metadata. +- `2.0-FindIntentAppDByResultChannel2`: Calls `fdc3.findIntent("sharedTestingIntent2", testContextY, "channel")`. Receives promise containing an `AppIntent` with metadata containing `sharedTestingIntent1` and only **F** app metadata. + +### Raise Intent (Ignoring any result) + +| App | Step | Details | +|-------|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent1")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set.** | + +- `2.0-RaiseIntentSingleResolve`: Perform above test +- `2.0-RaiseIntentTargetedAppResolve`: Repeat the above test, but: + - In the first step use `fdc3.raiseIntent("sharedTestingIntent1", testContextX, {"appID": ""})` to start app B, + - Otherwise, as above. +- `2.0-RaiseIntentTargetedInstanceResolveOpen`: Repeat the above test, but: + - Before the first step, use `let appIdentifier = await fdc3.open({appId: ""})` to start A and retrieve its `AppIdentifier` with instance details. + - Then in the first step, use `fdc3.raiseIntent("aTestingIntent", testContextX, appIdentifier)` to target the running instance of app A. + - Confirm that the intent is delivered to the correct instance and that another instance is NOT started. Otherwise, as above. +- `2.0-RaiseIntentTargetedInstanceResolveFindInstances`: Repeat the above test, but: + - Before the first step, use `let appIdentifier = await fdc3.open({appId: ""})` to start A. + - Then use `const instances = await fdc3.findInstances({appId: ""})` to retrieve a list of instances of app A. Confirm that only one is present and retrieve its `AppIdentifier`, confirming that it contains an `instanceId` field that matches that returned by the `fdc3.open` call. + - Then in the first step, use `fdc3.raiseIntent("aTestingIntent", testContextX, appIdentifier)` to target the running instance of app A. + - Confirm that the intent is delivered to the correct instance and that another instance is NOT started. Otherwise, as above. +- `2.0-RaiseIntentFailedResolve`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextY)`. Note that no app supports this intent and context combination.** + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound`. +- `2.0-RaiseIntentFailTargetedAppResolve1`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextY, {appId: ""})`. + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound`. +- `2.0-RaiseIntentFailTargetedAppResolve2`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextX, {appId: "NonExistentApp"})`. + - You should receive a JavaScript Error with the message `ResolveError.TargetAppUnavailable`. +- `2.0-RaiseIntentFailTargetedAppResolve3`: Perform above test, but: + - Use `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`. + - You should receive a JavaScript Error with the message `ResolveError.IntentDeliveryFailed` (as this app is configured for the intent and context pair, but does not add any intent listeners). + - **Note: Test will need an extended timeout to allow for this to be returned in time by the desktop agent, which will have a vendor-defined timeout.** +- `2.0-RaiseIntentFailTargetedAppResolve4`: Perform above test, but: + - `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})` + - You should receive a JavaScript Error with the message `ResolveError.IntentDeliveryFailed` (as this app is configured for the intent and context pair, but adds intent listeners of the wrong type. + - **Note: Test will need an extended timeout to allow for this to be returned in time by the desktop agent, which will have a vendor-defined timeout.** +- `2.0-RaiseIntentFailTargetedAppInstanceResolve1`: Perform above test, but: + - First spawn an instance of App **A** and collect its `AppIdentifier` with `const appIdentifier = await fdc3.open({appId: ""})`. + - Then use `fdc3.raiseIntent("aTestingIntent", testContextY, appIdentifier)` to target that instance. + - You should receive a JavaScript Error with the message `ResolveError.NoAppsFound` (since A doesn't support this context type). +- `2.0-RaiseIntentFailTargetedAppInstanceResolve2`: Perform above test, but: + - Use `fdc3.raiseIntent("aTestingIntent", testContextX, {appId: "", instanceId "NonExistentInstanceId"})`. + - You should receive a JavaScript Error with the message `ResolveError.TargetInstanceUnavailable`. + +### Raise Intent Result (void result) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("aTestingIntent", testContextX)`
starts app A. | +| A | 2. Receive Intent & Context | After starting up, A runs `fdc3.addIntentListener("aTestingIntent")` to register its listener.
It then receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App A's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| A | 5. return void | A should return `void` after a short delay (e.g. 5 seconds). | +| Test | 6. receive void result | The promise received by Test from `resolution.getResult()` should resolve to void. Confirm that the promise could be retrieved before the handler function returned and that the result was received *after* the result was returned by A, NOT before. I.e. confirm that `resolution.getResult()` does NOT block until the result is returned, but rather returns a promise that can be awaited. | + +- `2.0-RaiseIntentVoidResult5secs`: Perform above test +- `2.0-RaiseIntentVoidResult0secs`: Perform above test, but A should return its result immediately (no delay). Ignore test step 6 (as there is too little time between the IntentResolution and IntentHandler completing). +- `2.0-RaiseIntentVoidResult61secs`: Perform above test, but A should return its result **after 61 seconds** (arbitrary delay to test timeout does NOT occur) + +### Raise Intent Result (Context result) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise | `fdc3.raiseIntent("sharedTestingIntent1", testContextY)`
starts app **B**. | +| B | 2. Receive Intent & Context | After starting up, B runs `fdc3.addIntentListener("sharedTestingIntent1")` to register its listener.
It then receives `testContextY`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App B's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| B | 5. return `testContextY` | B should return a `testContextY` instance after a short delay (e.g. 5 seconds). | +| Test | 6. receive context result | The promise received by Test from `resolution.getResult()` should resolve to the `testContextY` instance. Confirm that the promise could be retrieved before the handler function returned and that the result was received *after* the result was returned by B, NOT before. I.e. confirm that `resolution.getResult()` does NOT block until the result is returned, but rather returns a promise that can be awaited. | + +- `2.0-RaiseIntentContextResult5secs`: Perform the above test. +- `2.0-RaiseIntentContextResult0secs`: Perform the previous test but B should return its result immediately (no delay). +- `2.0-RaiseIntentContextResult61secs`: As above, but B should return its result **after 61 seconds** (arbitrary delay to test timeout does NOT occur) + +### Raise Intent Result (Channel results) + +| App | Step | Details | +|-------|-----------------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise Intent | Test raises an intent with `fdc3.raiseIntent("sharedTestingIntent2", testContextY, {appId: ""})`
starts app E. | +| E | 2. Receive Intent & Context | After starting up, E runs `fdc3.addIntentListener("sharedTestingIntent2")` to register its listener.
It them receives `testContextY`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App E's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| E | 5. return Channel | E should retrieve a Channel object via `fdc3.getOrCreateChannel("someChannelName")` and return it immediately. | +| Test | 6. receive Channel result | The promise received by Test from `resolution.getResult()` should resolve to a `Channel` object with the expected id. Confirm that the `type` of the Channel object is "app". +| Test | 7. addContextListener | Add a context listener to the Channel object via `channelObj.addContextListener("testContextZ", handler)` | +| E | 8. broadcast context | After a short delay (of a few seconds) E should broadcast a `testContextZ` context object over the channel, including an `id` field with a unique identifier set (e.g. a uuid). | +| Test | 9. receive context | Test should receive the context broadcast by E and confirm that it contains the expected `id` value. | + +- `2.0-RaiseIntentChannelResult`: Perform the above test +- `2.0-RaiseIntentPrivateChannelResult`: Perform the above test, but: + - Substitute app F throughout - which returns a PrivateChannel result instead of channel. + - At step 5, the PrivateChannel should be created via`fdc3.createPrivateChannel()`. + - At step 6 confirm that the type of the channel is "private". + +### PrivateChannels cannot be accessed as app channels + +| App | Step | Details | +|-------|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Create a private channel | Test creates a PrivateChannel with `const privChan = await fdc3.createPrivateChannel()`. Confirm that the Channel has an `id`. +| Test | 2. Confirm private channel `id` is unique | Test creates a second PrivateChannel with `const privChan = await fdc3.createPrivateChannel();`. Confirm that the Channel has an `id` and that it is distinct from the first channel created. | +| Test | 3. Retrieve as app channel | Attempt to retrieve the channels as App Channels with `const appChan = await fdc3.getOrCreateChannel(privChan.id)` this should fail with `ChannelError.AccessDenied` | +| Test | 4. Raise Intent & await result | Start app J and pass it the id of the second PrivateChannel with `fdc3.raiseIntent("privateChannelIsPrivate", privateChannelDetails)`, where the context object contains the id of the channel to attempt to retrieve. An IntentResolution should be returned and App J should start. Wait for a result to be returned via `await resolution.getResult()`. +| J | 5. Receive Intent & Context | J should add an Intent Listener and receive the context with `fdc3.addIntentListener("privateChannelIsPrivate", handler)` | +| J | 6. Retrieve as app channel | J should attempt to retrieve the channel as an App Channel by `id` with `const appChan = await fdc3.getOrCreateChannel("")` this should fail with `ChannelError.AccessDenied`. Return a `privateChannelisPrivateResult` back to Test to complete the test. | +| Test | 7. Receive result | Test receives the result back from J and confirms that the test was passed. | + +- `2.0-PrivateChannelsAreNotAppChannels`: Perform the above test + +### PrivateChannel Lifecycle Events + +| App | Step | Details | +|-------|-----------------|---------------------------------------------------------------------------------------------------| +| Test | 1. Raise intent | Test raises an intent with `fdc3.raiseIntent(‘"kTestingIntent", testContextX, {appId: ""})`
starts app K. | +| K | 2. Receive Intent & Context | After starting up, K runs `fdc3.addIntentListener("kTestingIntent")` to register its listener.
It them receives `testContextX`, matching that sent by Test | +| Test | 3. IntentResolution | The `raiseIntent` call returns an `IntentResolution` Object with an `AppIdentifier` as the `source field` with App K's `appId` and `instanceId` set. | +| Test | 4. await results | Test should `await resolution.getResult()` on the `IntentResolution` object returned in the previous step. A promise should be returned quickly. | +| K | 5. Create PrivateChannel and setup event listeners | K should create a `PrivateChannel` object via `const privChan = await fdc3.createPrivateChannel()`,
it should then add listeners for the 3 events offered + a context listener via:
- `const listener1 = await privChan.onAddContextListener(handler1);`
- `const listener2 = await privChan.onUnsubscribe(handler2);`
- `const listener3 = await privChan.onDisconnect(handler3);`
- `const listener4 = await privChan.addContextListener("testContextX", handler4)`
it should then return the `PrivateChannel`. | +| Test | 6. receive PrivateChannel | The promise received by Test from `resolution.getResult()` should resolve to a `PrivateChannel` object. Confirm that the `type` of the Channel object is "private". +| Test | 7. addContextListener | Test should add a context listener to the PrivateChannel object via `const listener1 = privChan.addContextListener("testContextZ", handler)` | +| K | 8. Receive event & broadcast context | The `onAddContextListener` handler (`listener1`) added in step 5 should fire after Test adds its listener. Once it has, K should broadcast a short stream of `testContextZ` objects, with consecutive integer values in them (e.g. 1-5). | +| Test | 9. Unsubscribe listener | Test should confirm receipt of the expected context objects, in the expected order, broadcast by K. It should then remove its context listener with `listener1.unsubscribe().` | +| K | 10. Receive unsubscribe event | The event handler registered by K via `onUnsubscribe` should fire. If it does not and the test moves to a subsequent step, K should indicate this to the test runner (failing the test).| +| Test | 11. Broadcast context | Test should broadcast at least one `testContextX` object via the PrivateChannel (back to K). | +| K | 12. Receive context | K should confirm receipt of the expected context. If it does not and the test moves to a subsequent step K should indicate this to the test runner (failing the test).| +| Test | 13. re-run addContextListener | Test should (again) add a context listener to the PrivateChannel object via `const listener2 = privChan.addContextListener("testContextZ", handler)` | +| K | 14. Receive event & broadcast context | The `onAddContextListener` handler added in step 5 should (again) fire after Test adds its listener. Once it has, K should again broadcast a short stream of `testContextZ` objects, with consecutive integer values in them (e.g. 6-10). | +| Test | 15. Disconnect | Test should (again) confirm receipt of the expected context objects, in the expected order, broadcast by K. It should then disconnect from the channel with [`privChan.disconnect().`](https://fdc3.finos.org/docs/api/ref/PrivateChannel#disconnect) | +| K | 16. Receive events & cleanup | The `onUnsubscribe` handler added in step 5 should (again) fire after Test calls `privChan.disconnect()`. Subsequently, the `onDisconect` handler also added in step 5 should fire. Once it has, K can unsubscribe its listeners, indicate to the test runner that all steps were completed and close. | + +- `2.0-PrivateChannelsLifecycleEvents`: Perform the above test. + +### Resolving Ambiguous Intents + +FDC3 Desktop Agent MUST provide a method of resolving ambiguous intents (i.e. those that might be resolved by multiple applications) or unspecified intents (calls to raiseIntentForContext that return multiple options). This is often accomplished by providing a user interface allowing the user to select the desired target application or intent and application. + +As the methods of resolving ambiguous intents are often user interactive, it is either difficult or impossible to implement an automated test for this. Hence, manual tests should be performed as a final step in a conformance test. These tests are based on the same applications defined for and used in other intent tests - however a separate manual test app should be provided to enable the test. + +| App | Step | Details | +|---|---|---| +| Test | 1. Raise Ambiguous Intent | `fdc3.raiseIntent("sharedTestingIntent2", testContextY)` | +| User | 2. Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E`,`F`,`G`,`H` and `I`. | + +- `2.0-ResolveAmbiguousIntentTarget`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Raise Ambiguous Intent | `fdc3.raiseIntentForContext(testContextY)` | +| User | 2. Chooser Interaction | Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E`,`F`,`G`,`H` and `I`. | + +- `2.0-ResolveAmbiguousContextTarget`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Open 4 Apps | Use `fdc3.open()` to open 2 instances of App `E` and 2 instances of `F`. | +| Test | 2. Raise Ambiguous Intent | `fdc3.raiseIntent("sharedTestingIntent2", testContextY)` | +| User | 3. Chooser Interaction | Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E (1)`,`F (1)`,`E (2)`,`F (2)` and options to open `G`, `H` and `I` | + +- `2.0-ResolveAmbiguousIntentTargetMultiInstance`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. + +| App | Step | Details | +|---|---|---| +| Test | 1. Open 4 Apps | Use `fdc3.open()` to open 2 instances of App `E` and 2 instances of `F`. | +| Test | 2. Raise Ambiguous Intent | `fdc3.raiseIntentForContext(testContextY)` | +| User | 3. Chooser Interaction | Chooser Interaction | A method of resolving the ambiguous request is provided (such as a User Interface allowing the user to choose an application or instance) for choosing one of `E (1)`,`F (1)`,`E (2)`,`F (2)` and options to open `G`, `H` and `I` | + +- `2.0-ResolveAmbiguousContextTargetMultiInstance`: Perform above steps to invoke intent resolution for an unspecified target with multiple options. Confirm that test is able to complete successfully. diff --git a/website/versioned_docs/version-2.1/api/conformance/Metadata-Tests.md b/website/versioned_docs/version-2.1/api/conformance/Metadata-Tests.md new file mode 100644 index 000000000..456b4bff3 --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/Metadata-Tests.md @@ -0,0 +1,68 @@ +--- +id: Metadata-Tests +sidebar_label: Metadata Tests +title: Metadata Tests +hide_title: true +--- + +# Metadata & Instance Test Cases + +You will need to pre-populate the AppDirectory with the following items: + +| App | Required Metadata | +|-----|------------------------------------------| +| A | Generic AppD Record which contains at least the following fields:
- `name`
- `version`
- `title`
- `tooltip`
- `description`
- `icons` (`Array`)
- `screenshots` (`Array`)
- `interop.intents.listensFor` (`aTestingIntent` with at least context type `testContextX`) | + +## Retrieve `AppMetadata` ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.getAppMetadata | Retrieve metadata for the configured app A with
`const metadata1 = await fdc3.getAppMetadata({appId: ""})` | +| Test | 2.Confirm | Compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. An `instanceId` should NOT be set | + +- `GetAppMetadata`: perform the above steps + +## Instance Metadata ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Open a first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 3.getAppMetadata1 | Retrieve metadata for the first instance of the app with
`const metadata1 = fdc3.getAppMetadata(appIdentifier1)` | +| Test | 4.Confirm1 | Compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. | +| Test | 5.getAppMetadata2 | Retrieve metadata for the second instance of the app with
`const metadata2 = fdc3.getAppMetadata(appIdentifier2)` | +| Test | 6.Confirm2 | An `instanceId` should be provided, confirm that it matches the one in `appIdentifier2` | + +- `AppInstanceMetadata`: Perform the above steps + +## Finding Instances ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Open the first instance of App A using
`const appIdentifier1 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId`. | +| Test | 2.Open2 |Open a second instance of App A using
`const appIdentifier2 = await fdc3.open({appId: ""})`
and confirm that its `AppIdentifier` contains an `instanceId` and that its value differs from that returned for the first instance. | +| Test | 3.FindInstances | Retrieve details of open instances of app A with
`let instances = await fdc3.findInstances({appId: ""})`
confirm that both `appIdentifier1` and `appIdentifier2` are both present in the array. | +| Test | 4.RaiseIntent | Use `appIdentifier1` to raise an intent and target that instance, with
`const resolution = fdc3.raiseIntent("aTestingIntent", {"type": "testContextX"}, appIdentifier1)` | +| Test | 5.Confirm1 | Check that `resolution.source` matches `appIdentifier1` | +| A | 6.ConfirmReceipt | Ensure that the instance of app A represented by `appIdentifier1` received the raised intent | + +- `FindInstances`: Perform the above steps + +## Getting Info For The Agent + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.getInfo |Retrieve the `ImplementationMetadata` for the DesktopAgent with
- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `let implMetadata = fdc3.getInfo()`
- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.getInfo().then((implMetadata) => { subsequent steps }`
**Note that the use of `then` is deliberate and intended to confirm that a promise returned (as this function switched from synchronous to asynchronous in 2.0)**| +| Test | 2.CheckVersion | Check that the `fdc3Version` variable is present and at or greater than:
- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) 1.2
- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) 2.0
(which you can do with the [`versionIsAtLeast` function from FDC3's Methods.ts](https://github.com/finos/FDC3/blob/add64f8302c6dcdc8437cf0e245101e927b69ec2/src/api/Methods.ts#L207):
`const isFDC3v2 = versionIsAtLeast(implMetadata, "2.0")` | +| Test | 3.CheckProvider | Check that the `provider` variable is present and not an empty string | +| Test | 4.CheckFeatures | ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Check that the `optionalFeatures`, `optionalFeatures.OriginatingAppMetadata` and `optionalFeatures.UserChannelMembershipAPIs` variables are all present and that the latter two provide boolean values | + +- ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `GetInfo1`: Perform the above steps + +| App | Step | Details | +|-----|----------------|---------------------------------------------------------------------------------------------------| +| Test | 1.Open1 | Start an instance of App A with
`const appIdentifier1 = await fdc3.open({appId: ""})`
retrieve its `AppIdentifier` with instance details. Confirm that the `AppIdentifier` contains both an `appId` and `instanceId` | +| A | 2.getInfo | Retrieve the `ImplementationMetadata` for the DesktopAgent with:
`fdc3.getInfo().then((implMetadata) => { ... subsequent steps ...}`
This should include `AppMetadata` for the retrieving app. | +| A + Test | 3.Confirm | Check that `implMetadata.appMetadata` contains an `appId` and `instanceId` matching that retrieved in the first step (will require transmission of the details from A to Test or vice-versa). Also compare the `AppMetadata` object to the expected definition for the fields provided above during setup and ensure that the metadata matches. | + +- ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `GetInfo2`: Perform the above steps. diff --git a/website/versioned_docs/version-2.1/api/conformance/Open-Tests.md b/website/versioned_docs/version-2.1/api/conformance/Open-Tests.md new file mode 100644 index 000000000..85e8f0aa6 --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/Open-Tests.md @@ -0,0 +1,54 @@ +--- +id: Open-Tests +sidebar_label: Open Tests +title: Open Tests +hide_title: true +--- + +# Open Tests + +## A Opens B + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App | App A calls a function (see below) to open a second app, B | +| A | 2. Check Metadata | Ensure that the correct app was opened | + +- `AOpensB1`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open(‘app B Name’)` +- `AOpensB2`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open({name: “”})` +- `AOpensB3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.open({name: “”, appId: “”})` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.open({appId: “”})` +- `AOpensB4`: ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) **A** uses an `AppIdentifier` to open B and retrieves an updated `AppIdentifier` with an `instanceId` set via `const instanceIdentifier = await fdc3.open({appId: “”})`. Ensure that the `appId` matches that requested and that an `instanceId` property has been set. + +## A Fails To Open Another App + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App | App A calls a function (see below) to try and open a non-existent app | +| A | 2. Check Error Response | ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.open` throws an Error with the message "App Not Found"
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.open` returns a promise that rejects with an Error with the message "App Not Found" | + +- `AFailsToOpenB1`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open(‘non existent app’)` +- `AFailsToOpenB2`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open({name: “non existent app”})` +- `AFailsToOpenB3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.open({name: “non existent app”, appId: “non existent app”})` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.open({appId: “”})` + +## A Opens B With Context + +| App | Step | Description | +|-----|-----------------|----------------------------------------------------------| +| A | 1. Opening App |App A opens app B with an `fdc3.instrument` Context Object by calling a function (see below) | +| B | 2. Receive Context | Add an untyped context listener via:
`fdc3.addContextListener(null, handler)`
B receives an `fdc3.instrument` Context Object matching that passed to the `fdc3.open() call made by A | + +- `AOpensBWithContext1`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open(‘app B Name', )` +- `AOpensBWithContext2`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) **A** uses `fdc3.open({name: “”}, )` +- `AOpensBWithContext3`: **A** uses an `AppMetadata` or `AppIdentifier` to open B, via: + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.open({name: “”, appId: “”}, )` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.open({appId: “”}, )` +- `AOpensBWithSpecificContext`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Perform AOpensBWithContext3 but replace **B**s call with `fdc3.addContextListener('fdc3.instrument', handler)` +- `AOpensBMultipleListen`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Perform `AOpensBWithSpecificContext` but **B** should perform an additional `fdc3.addContextListener('fdc3.contact', handler)` prior to the existing `addContextListener` for `fdc3.instrument`. The correct context listener should receive the context, and the promise completes successfully. +- `AOpensBWithWrongContext`: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Perform `AOpensBWithSpecificContext` but **B** should add a context listener for the wrong context type (e.g. `fdc3.dummyType`) instead of the expected type in step 2. + - Confirm that NO context is received. + - ![1.2](https://img.shields.io/badge/FDC3-1.2-green) the `fdc3.open` call throws an Error with message `AppTimeout` + - ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) The promise returned to **A** by `fdc3.open` rejects with an Error with message `AppTimeout` diff --git a/website/versioned_docs/version-2.1/api/conformance/Overview.md b/website/versioned_docs/version-2.1/api/conformance/Overview.md new file mode 100644 index 000000000..894504df9 --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/Overview.md @@ -0,0 +1,25 @@ +--- +id: Conformance-Overview +sidebar_label: Overview +title: FDC3 Conformance Tests +hide_title: true +--- + +# FDC3 Conformance Tests + +This section contains test definitions that are used to test for conformance of a Desktop Agent API implementation with FDC3. + +:::info +As FDC3 2.1 does not introduce changes to the Desktop Agent API, the conformance test set for FDC3 2.0 remains current for this version. Please see the [FDC3 2.1 Changelog entry](https://github.com/finos/FDC3/blob/main/CHANGELOG.md#fdc3-standard-21---2023-09-13) for more details. +::: + +You can find the implementation of these tests in the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework) project. + +There are currently 6 sections to the tests. Where tests apply to a particular version of FDC3, this is labelled with icons in the header, like so: ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +- [Basic Tests](Basic-Tests.md) +- [Open Tests](Open-Tests.md) +- [User Channel Tests](User-Channel-Tests.md) +- [App Channel Tests](App-Channel-Tests.md) +- [Metadata Tests](Metadata-Tests.md) +- [Intents Tests](Intents-Tests.md) diff --git a/website/versioned_docs/version-2.1/api/conformance/User-Channel-Tests.md b/website/versioned_docs/version-2.1/api/conformance/User-Channel-Tests.md new file mode 100644 index 000000000..89f1b03cf --- /dev/null +++ b/website/versioned_docs/version-2.1/api/conformance/User-Channel-Tests.md @@ -0,0 +1,57 @@ +--- +id: User-Channel-Tests +sidebar_label: User Channel Tests +title: User Channel Tests +hide_title: true +--- + +# User Channel Tests ![1.2](https://img.shields.io/badge/FDC3-1.2-green) ![2.0](https://img.shields.io/badge/FDC3-2.0-blue) + +_NB: User Channels were called System Channels in FDC3 1.2. The new terminology is used in this specification_ + +## Basic Broadcast + +| App | Step |Details | +|-----|--------------------|----------------------------------------------------------------------------------| +| A | 1.addContextListener |A adds an _unfiltered_ Context Listener using `addContextListener(null, handler)`.
![1.2](https://img.shields.io/badge/FDC3-1.2-green) A `Listener` object is returned
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) A promise resolving a `Listener` object is returned
Check that this has an `unsubscribe` method. | +| A | 2.joinUserChannel |A joins the first available (non-global) user channel. The available Channels are retrieved with:
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.getSystemChannels()` _Check channels are returned._
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.getUserChannels()`
The first channel (that does not have the id 'global') is joined with:
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `fdc3.joinChannel()` _Check channels are returned._
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `fdc3.joinUserChannel()` | +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | B broadcasts an `fdc3.instrument` context to the channel using `fdc3.broadcast()`.
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Check a `void` promise is returned. | +| A | 5.Receive Context | A receives the instrument object, matching the one broadcast by B. | + +- `UCBasicUsage1` Perform above test +- `UCBasicUsage2` Perform steps in order: 2,1,3,4,5 to confirm that the order of `joinUserChannel` and `addContextListener` calls doesn't matter +- `UCBasicUsage3` Perform steps in order: 3,4,1,2,5 to confirm that the current context is automatically received on joining a channel. +- `UCBasicUsage4` Perform steps in order: 3,4,2,1,5 to confirm that the current context is automatically received on adding a context listener to an already joined a channel. + +## Filtered Broadcast + +| App | Step |Details | +|-----|--------------------|----------------------------------------------------------------------------------| +| A | 1.addContextListener |A adds a `fdc3.instrument` _typed_ Context Listener using `addContextListener("fdc3.instrument", handler)`.
![1.2](https://img.shields.io/badge/FDC3-1.2-green) A `Listener` object is returned
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) A promise resolving a `Listener` object is returned
Check that this has an `unsubscribe` method.| +| A | 2.joinUserChannel |A joins the first available user channel using:
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `getSystemChannels()` Check channels are returned.
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `getUserChannels()` Check **user** channels are returned.
Call `fdc3.joinChannel()` on the first non-global channel.| +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | B broadcasts:
1.`fdc3.broadcast()`.
2. `fdc3.broadcast(
)`
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) Check a `void` promise is returned. | +| A | 5.Receive Context | A receives the `fdc3.instrument` object, matching the one broadcast by B.
Check that the `fdc3.contact` is NOT received. | + +- `UCFilteredUsage1` Perform above test +- `UCFilteredUsage2` Perform steps in order: 2,1,3,4,5 +- `UCFilteredUsage3` Perform steps in order: 3,4,1,2,5 +- `UCFilteredUsage4` Perform steps in order: 3,4,2,1,5 + +## Broadcast With Multiple Listeners + +| App | Step | Details | +|-----|--------------------|-------------------------------------------------------------------------------------------------------------| +| A | 1.addContextListeners | A sets up two Context Listeners. One for `fdc3.instrument` and one for `fdc3.contact` by calling: `addContextListener ("fdc3.instrument", handler)`
`addContextListener ("fdc3.contact", handler)`
![1.2](https://img.shields.io/badge/FDC3-1.2-green) A `Listener` object is returned for each.
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) A promise resolving a `Listener` object is returned for each.
Check that this has an `unsubscribe` method for each. | +| A | 2.joinUserChannel |A joins the first available user channel using:
![1.2](https://img.shields.io/badge/FDC3-1.2-green) `getSystemChannels()` Check channels are returned.
![2.0](https://img.shields.io/badge/FDC3-2.0-blue) `getUserChannels()` Check **user** channels are returned.
Call `fdc3.joinChannel()` on the first non-global channel.| +| B | 3.joinUserChannel |B joins the same channel as A, via the same process in 2. | +| B | 4.Broadcast | `fdc3.broadcast()`
`fdc3.broadcast()` . | +| A | 5.Receive Context | A's `fdc3.instrument` object matches the one broadcast by B, and arrives on the correct listener.
A's `fdc3.contact` object matches the one broadcast by B, and arrives on the correct listener. | + + - `UCFilteredUsage5`: Perform above test + - `UCFilteredUsage6`: Perform above test, except B will join a _different_ channel to A. Check that you _don't_ receive anything. + - `UCFilteredUsageChange`: Perform above test, except that after joining, **A** changes channel to a _different_ channel via a further call to `fdc3.joinUserChannel`. Check that **A** does NOT receive anything. + - `UCFilteredUsageUnsubscribe`: Perform above test, except that after joining, **A** then `unsubscribe()`s from the channel using the `listener.unsubscribe` function. Check that **A** does NOT receive anything. + - `UCFilteredUsageLeave`: Perform above test, except that immediately after joining, **A** _leaves the channel_, and so receives nothing. + - `UCFilteredUsageNoJoin`: Perform the above test, but skip step 2 so that **A** does NOT join a channel. Confirm that the _current channel_ for **A** is NOT set before continuing with the rest of the test. **A** should receive nothing. diff --git a/website/versioned_docs/version-2.1/fdc3-compliance.md b/website/versioned_docs/version-2.1/fdc3-compliance.md index 64f242828..7cfcbd1d6 100644 --- a/website/versioned_docs/version-2.1/fdc3-compliance.md +++ b/website/versioned_docs/version-2.1/fdc3-compliance.md @@ -70,6 +70,26 @@ FDC3 adopts the following experimental features policy: 5. Experimental features are exempted from the normal versioning and deprecation policies that govern changes to FDC3. I.e. breaking changes may be made to experimental features between versions of the Standard without a major version release. 6. The experimental designation may be removed from a feature in a minor version release (as this will be considered an additive change). +## Conformance testing + +The FDC3 Standards include a set of [definitions for conformance tests](api/conformance/Conformance-Overview) that may be used to determine if a Desktop Agent API implementation conforms to a particular Standard version, to help disambiguate complex parts of the FDC3 Standard and to enable test-driven development of a Desktop Agent implementation. + +The current set of tests focus on the Desktop Agent API and the interface to it. Tests are not yet defined for the App Directory API or Bridging API Parts of the FDC3 Standard, hence, conformance to those parts of the Standard must be determined manually. + +:::info +As FDC3 2.1 does not introduce changes to the Desktop Agent API, the conformance test set for FDC3 2.0 remains current for this version. Please see the [FDC3 2.1 Changelog entry](https://github.com/finos/FDC3/blob/main/CHANGELOG.md#fdc3-standard-21---2023-09-13) for more details. +::: + +The FDC3 Conformance tests are implemented for JavaScript/TypeScript web applications by the [FDC3 Conformance Framework](https://github.com/finos/FDC3-conformance-framework). Desktop Agent implementors working with web interfaces (Desktop Agent Preload or Desktop Agent Proxy) can clone the conformance framework and run the tests locally to determine if their agent is compliant with the Standard. + +Once a Desktop Agent has passed the conformance tests locally, its authors can [apply for a formal certification of compliance with the Standard from FINOS](https://github.com/finos/FDC3-conformance-framework/blob/main/instructions.md). Please note the [Terms and Conditions](https://github.com/finos/FDC3-conformance-framework/blob/main/terms-conditions/FDC3-Certified-Terms.md) of the Conformance Program. + +import badge_12 from '/img/community/certified-1.2.png'; +import badge_20 from '/img/community/certified-2.0.png'; + +Certified conformant with FDC3 1.2 badge +Certified conformant with FDC3 2.0 badge + ## Intellectual Property Claims Recipients of this document are requested to submit, with their comments, notification of diff --git a/website/versioned_sidebars/version-2.0-sidebars.json b/website/versioned_sidebars/version-2.0-sidebars.json index cc3fe6972..e3fe419f7 100644 --- a/website/versioned_sidebars/version-2.0-sidebars.json +++ b/website/versioned_sidebars/version-2.0-sidebars.json @@ -22,7 +22,20 @@ "api/ref/Globals", "api/ref/Types", "api/ref/Metadata", - "api/ref/Errors" + "api/ref/Errors", + { + "type": "category", + "label": "Conformance Tests", + "items": [ + "api/conformance/Conformance-Overview", + "api/conformance/Basic-Tests", + "api/conformance/App-Channel-Tests", + "api/conformance/User-Channel-Tests", + "api/conformance/Open-Tests", + "api/conformance/Intents-Tests", + "api/conformance/Metadata-Tests" + ] + } ] }, { diff --git a/website/versioned_sidebars/version-2.1-sidebars.json b/website/versioned_sidebars/version-2.1-sidebars.json index 60904acb9..0e831ef23 100644 --- a/website/versioned_sidebars/version-2.1-sidebars.json +++ b/website/versioned_sidebars/version-2.1-sidebars.json @@ -22,7 +22,20 @@ "api/ref/Globals", "api/ref/Types", "api/ref/Metadata", - "api/ref/Errors" + "api/ref/Errors", + { + "type": "category", + "label": "Conformance Tests", + "items": [ + "api/conformance/Conformance-Overview", + "api/conformance/Basic-Tests", + "api/conformance/App-Channel-Tests", + "api/conformance/User-Channel-Tests", + "api/conformance/Open-Tests", + "api/conformance/Intents-Tests", + "api/conformance/Metadata-Tests" + ] + } ] }, {