From 16d447f221b95bd6e19c8af9e22d3f074c87b239 Mon Sep 17 00:00:00 2001 From: Ruben Verborgh Date: Wed, 25 Nov 2020 11:19:55 +0100 Subject: [PATCH] test: Add WebSockets integration test. --- package-lock.json | 12 +-- package.json | 3 +- test/configs/Util.ts | 17 +++++ test/configs/websockets.json | 48 ++++++++++++ test/integration/WebSocketsProtocol.test.ts | 84 +++++++++++++++++++++ 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 test/configs/websockets.json create mode 100644 test/integration/WebSocketsProtocol.test.ts diff --git a/package-lock.json b/package-lock.json index fe5c21ee2..e0a82c13b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1245,9 +1245,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.164", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.164.tgz", - "integrity": "sha512-fXCEmONnrtbYUc5014avwBeMdhHHO8YJCkOBflUL9EoJBSKZ1dei+VO74fA7JkTHZ1GvZack2TyIw5U+1lT8jg==" + "version": "4.14.165", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz", + "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==" }, "@types/lru-cache": { "version": "5.1.0", @@ -2488,9 +2488,9 @@ "dev": true }, "componentsjs": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-3.6.0.tgz", - "integrity": "sha512-G3lMrIbE7iiZpERoPXnxM0aDopq9q1s1C5aIUrnHW3rcRDa3kcCytc4ASt5aFRjNiwBubivcMfJsvF2ihXg7jQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-3.6.1.tgz", + "integrity": "sha512-Qnnqo9Lx7yBhK8ttkwjFYAkY300P9gvr4S9q50tWU/O01dzSdBxX/rvvx9HBa3PxMOkV1UaBkmztU7Rmq92uZQ==", "requires": { "@types/lodash": "^4.14.56", "@types/minimist": "^1.2.0", diff --git a/package.json b/package.json index c25dd847e..a95c33582 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@types/yargs": "^15.0.5", "arrayify-stream": "^1.0.0", "async-lock": "^1.2.4", - "componentsjs": "^3.6.0", + "componentsjs": "^3.6.1", "cors": "^2.8.5", "express": "^4.17.1", "fetch-sparql-endpoint": "^1.8.0", @@ -113,6 +113,7 @@ "@typescript-eslint/eslint-plugin": "^4.1.1", "@typescript-eslint/parser": "^4.1.1", "componentsjs-generator": "^1.6.0", + "cross-fetch": "^3.0.6", "eslint": "^7.9.0", "eslint-config-es": "^3.20.3", "eslint-import-resolver-typescript": "^2.3.0", diff --git a/test/configs/Util.ts b/test/configs/Util.ts index 97b409829..b401e8861 100644 --- a/test/configs/Util.ts +++ b/test/configs/Util.ts @@ -1,4 +1,6 @@ import { join } from 'path'; +import * as Path from 'path'; +import { Loader } from 'componentsjs'; import type { BodyParser, DataAccessor, @@ -173,3 +175,18 @@ export const getBasicRequestParser = (bodyParsers: BodyParser[] = []): BasicRequ */ export const getWebAclAuthorizer = (store: ResourceStore, aclManager = new UrlBasedAclManager()): WebAclAuthorizer => new WebAclAuthorizer(aclManager, store); + +/** + * Returns a component instantiated from a Components.js configuration. + */ +export const instantiateFromConfig = async(componentUrl: string, configFile: string, + variables?: Record): Promise => { + // Initialize the Components.js loader + const mainModulePath = Path.join(__dirname, '../../'); + const loader = new Loader({ mainModulePath }); + await loader.registerAvailableModuleResources(); + + // Instantiate the component from the config + const configPath = Path.join(__dirname, configFile); + return loader.instantiateFromUrl(componentUrl, configPath, undefined, { variables }); +}; diff --git a/test/configs/websockets.json b/test/configs/websockets.json new file mode 100644 index 000000000..0d7f09c31 --- /dev/null +++ b/test/configs/websockets.json @@ -0,0 +1,48 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/presets/http.json", + "files-scs:config/presets/ldp/credentials-extractor.json", + "files-scs:config/presets/ldp/metadata-handler.json", + "files-scs:config/presets/ldp/operation-handler.json", + "files-scs:config/presets/ldp/permissions-extractor.json", + "files-scs:config/presets/ldp/response-writer.json", + "files-scs:config/presets/ldp/request-parser.json", + "files-scs:config/presets/ldp/websockets.json", + "files-scs:config/presets/representation-conversion.json", + "files-scs:config/presets/storage/backend/storage-memory.json", + "files-scs:config/presets/storage/routing/no-routing.json", + "files-scs:config/presets/storage-wrapper.json", + "files-scs:config/presets/cli-params.json" + ], + "@graph": [ + { + "@id": "urn:solid-server:default:HttpHandler", + "@type": "AuthenticatedLdpHandler", + "AuthenticatedLdpHandler:_args_requestParser": { + "@id": "urn:solid-server:default:RequestParser" + }, + "AuthenticatedLdpHandler:_args_credentialsExtractor": { + "@id": "urn:solid-server:default:CredentialsExtractor" + }, + "AuthenticatedLdpHandler:_args_permissionsExtractor": { + "@id": "urn:solid-server:default:PermissionsExtractor" + }, + "AuthenticatedLdpHandler:_args_authorizer": { + "@type": "AllowEverythingAuthorizer" + }, + "AuthenticatedLdpHandler:_args_operationHandler": { + "@id": "urn:solid-server:default:OperationHandler" + }, + "AuthenticatedLdpHandler:_args_responseWriter": { + "@id": "urn:solid-server:default:ResponseWriter" + } + }, + { + "@id": "urn:solid-server:default:RoutingResourceStore", + "PassthroughStore:_source": { + "@id": "urn:solid-server:default:MemoryResourceStore" + } + } + ] +} diff --git a/test/integration/WebSocketsProtocol.test.ts b/test/integration/WebSocketsProtocol.test.ts new file mode 100644 index 000000000..80184eb81 --- /dev/null +++ b/test/integration/WebSocketsProtocol.test.ts @@ -0,0 +1,84 @@ +import type { Server } from 'http'; +import fetch from 'cross-fetch'; +import WebSocket from 'ws'; +import type { HttpServerFactory } from '../../src/server/HttpServerFactory'; +import { instantiateFromConfig } from '../configs/Util'; + +const port = 6001; +const baseUrl = `http://localhost:${port}/`; + +describe('A server with the Solid WebSockets API', (): void => { + let server: Server; + + beforeAll(async(): Promise => { + const factory = await instantiateFromConfig( + 'urn:solid-server:default:ServerFactory', 'websockets.json', { + 'urn:solid-server:default:variable:port': port, + 'urn:solid-server:default:variable:base': baseUrl, + }, + ) as HttpServerFactory; + server = factory.startServer(port); + }); + + afterAll(async(): Promise => { + await new Promise((resolve, reject): void => { + server.close((error): void => error ? reject(error) : resolve()); + }); + }); + + it('returns a 200.', async(): Promise => { + const response = await fetch(baseUrl); + expect(response.status).toBe(200); + }); + + it('sets the Updates-Via header.', async(): Promise => { + const response = await fetch(baseUrl); + expect(response.headers.get('Updates-Via')).toBe(`ws://localhost:${port}`); + }); + + describe('when a WebSocket client connects', (): void => { + let client: WebSocket; + const messages = new Array(); + + beforeAll(async(): Promise => { + client = new WebSocket(`ws://localhost:${port}`, [ 'solid/0.1.0-alpha' ]); + client.on('message', (message: string): any => messages.push(message)); + await new Promise((resolve): any => client.on('open', resolve)); + }); + + afterAll((): void => { + client.close(); + }); + + afterEach((): void => { + messages.length = 0; + }); + + it('sends the protocol version.', (): void => { + expect(messages).toEqual([ + 'protocol solid/0.1.0-alpha', + 'warning Unstandardized protocol version, proceed with care', + ]); + }); + + describe('when the client subscribes to a resource', (): void => { + beforeAll(async(): Promise => { + client.send(`sub ${baseUrl}my-resource`); + await new Promise((resolve): any => client.once('message', resolve)); + }); + + it('acknowledges the subscription.', async(): Promise => { + expect(messages).toEqual([ `ack ${baseUrl}my-resource` ]); + }); + + it('notifies the client of resource updates.', async(): Promise => { + await fetch(`${baseUrl}my-resource`, { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: '{}', + }); + expect(messages).toEqual([ `pub ${baseUrl}my-resource` ]); + }); + }); + }); +});