From e3cf2f9469fdeb7abf2916209be3443e8bee3d87 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 26 Feb 2021 11:39:59 +0100 Subject: [PATCH] test: Add integration tests for dynamic pod creation Also fixed issue with pod template acl files --- config/presets/pod-dynamic.json | 4 +- templates/pod/.acl | 6 +- test/assets/templates/.acl | 6 +- test/integration/DynamicPods.test.ts | 133 ++++++++++++++++++ .../config/server-dynamic-unsafe.json | 81 +++++++++++ 5 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 test/integration/DynamicPods.test.ts create mode 100644 test/integration/config/server-dynamic-unsafe.json diff --git a/config/presets/pod-dynamic.json b/config/presets/pod-dynamic.json index a07fdfe32..5153d9b24 100644 --- a/config/presets/pod-dynamic.json +++ b/config/presets/pod-dynamic.json @@ -8,7 +8,9 @@ { "@id": "urn:solid-server:default:PodConfigurationStorage", "@type": "JsonFileStorage", - "JsonFileStorage:_filePath": "urn:solid-server:default:variable:podConfigJson", + "JsonFileStorage:_filePath": { + "@id": "urn:solid-server:default:variable:podConfigJson" + }, "JsonFileStorage:_locker": { "@id": "urn:solid-server:default:ResourceLocker" } diff --git a/templates/pod/.acl b/templates/pod/.acl index 86d27703c..a974c3810 100644 --- a/templates/pod/.acl +++ b/templates/pod/.acl @@ -6,7 +6,7 @@ <#public> a acl:Authorization; acl:agentClass foaf:Agent; - acl:accessTo ; + acl:accessTo <./>; acl:mode acl:Read. # The owner has full access to every resource in their pod. @@ -18,9 +18,9 @@ # Optional owner email, to be used for account recovery: {{#if email}}acl:agent ;{{/if}} # Set the access to the root storage folder itself - acl:accessTo ; + acl:accessTo <./>; # All resources will inherit this authorization, by default - acl:default ; + acl:default <./>; # The owner has all of the access modes allowed acl:mode acl:Read, acl:Write, acl:Control. diff --git a/test/assets/templates/.acl b/test/assets/templates/.acl index 86d27703c..a974c3810 100644 --- a/test/assets/templates/.acl +++ b/test/assets/templates/.acl @@ -6,7 +6,7 @@ <#public> a acl:Authorization; acl:agentClass foaf:Agent; - acl:accessTo ; + acl:accessTo <./>; acl:mode acl:Read. # The owner has full access to every resource in their pod. @@ -18,9 +18,9 @@ # Optional owner email, to be used for account recovery: {{#if email}}acl:agent ;{{/if}} # Set the access to the root storage folder itself - acl:accessTo ; + acl:accessTo <./>; # All resources will inherit this authorization, by default - acl:default ; + acl:default <./>; # The owner has all of the access modes allowed acl:mode acl:Read, acl:Write, acl:Control. diff --git a/test/integration/DynamicPods.test.ts b/test/integration/DynamicPods.test.ts new file mode 100644 index 000000000..361786f99 --- /dev/null +++ b/test/integration/DynamicPods.test.ts @@ -0,0 +1,133 @@ +import { mkdirSync } from 'fs'; +import type { Server } from 'http'; +import fetch from 'cross-fetch'; +import type { Initializer } from '../../src/init/Initializer'; +import type { HttpServerFactory } from '../../src/server/HttpServerFactory'; +import { joinFilePath } from '../../src/util/PathUtil'; +import { getTestFolder, instantiateFromConfig, removeFolder } from './Config'; + +const port = 6006; +const baseUrl = `http://localhost:${port}/`; +const rootFilePath = getTestFolder('dynamicPods'); +const podConfigJson = joinFilePath(rootFilePath, 'config-pod.json'); + +const configs: [string, any][] = [ + [ 'storage-memory.json', { + teardown: (): void => removeFolder(rootFilePath), + }], + [ 'storage-filesystem.json', { + teardown: (): void => removeFolder(rootFilePath), + }], +]; + +// Using the actual templates instead of specific test ones to prevent a lot of duplication +// Tests are very similar to subdomain/pod tests. Would be nice if they can be combined +describe.each(configs)('A dynamic pod server with template config %s', (template, { teardown }): void => { + let server: Server; + let initializer: Initializer; + let factory: HttpServerFactory; + const agent = { login: 'alice', webId: 'http://test.com/#alice', name: 'Alice Bob', template }; + const podUrl = `${baseUrl}${agent.login}/`; + + beforeAll(async(): Promise => { + const variables: Record = { + 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:port': port, + 'urn:solid-server:default:variable:rootFilePath': rootFilePath, + 'urn:solid-server:default:variable:podTemplateFolder': joinFilePath(__dirname, '../assets/templates'), + 'urn:solid-server:default:variable:podConfigJson': podConfigJson, + }; + + // Need to make sure the temp folder exists so the podConfigJson can be written to it + mkdirSync(rootFilePath); + + // Create and initialize the HTTP handler and related components + const instances = await instantiateFromConfig( + 'urn:solid-server:test:Instances', + 'server-dynamic-unsafe.json', + variables, + ) as Record; + ({ factory, initializer } = instances); + + // Set up the internal store + await initializer.handleSafe(); + + server = factory.startServer(port); + }); + + afterAll(async(): Promise => { + await new Promise((resolve, reject): void => { + server.close((error): void => error ? reject(error) : resolve()); + }); + await teardown(); + }); + + it('creates a pod with the given config.', async(): Promise => { + const res = await fetch(`${baseUrl}pods`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(agent), + }); + expect(res.status).toBe(201); + expect(res.headers.get('location')).toBe(podUrl); + }); + + it('can fetch the created pod.', async(): Promise => { + const res = await fetch(podUrl); + expect(res.status).toBe(200); + }); + + it('should not be able to read the acl file.', async(): Promise => { + const res = await fetch(`${podUrl}.acl`); + expect(res.status).toBe(401); + }); + + it('should be able to read acl file with the correct credentials.', async(): Promise => { + const res = await fetch(`${podUrl}.acl`, { + headers: { + authorization: `WebID ${agent.webId}`, + }, + }); + expect(res.status).toBe(200); + }); + + it('should be able to write to the pod now as the owner.', async(): Promise => { + let res = await fetch(`${podUrl}test`, { + headers: { + authorization: `WebID ${agent.webId}`, + }, + }); + expect(res.status).toBe(404); + + res = await fetch(`${podUrl}test`, { + method: 'PUT', + headers: { + authorization: `WebID ${agent.webId}`, + 'content-type': 'text/plain', + }, + body: 'this is new data!', + }); + expect(res.status).toBe(205); + + res = await fetch(`${podUrl}test`, { + headers: { + authorization: `WebID ${agent.webId}`, + }, + }); + expect(res.status).toBe(200); + await expect(res.text()).resolves.toBe('this is new data!'); + }); + + it('should not be able to create a pod with the same name.', async(): Promise => { + const res = await fetch(`${baseUrl}pods`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(agent), + }); + expect(res.status).toBe(409); + }); +}); diff --git a/test/integration/config/server-dynamic-unsafe.json b/test/integration/config/server-dynamic-unsafe.json new file mode 100644 index 000000000..dbfe40f21 --- /dev/null +++ b/test/integration/config/server-dynamic-unsafe.json @@ -0,0 +1,81 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", + "import": [ + "files-scs:config/presets/acl.json", + "files-scs:config/presets/http.json", + "files-scs:config/presets/identifiers/suffix-identifiers.json", + "files-scs:config/presets/init.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/middleware.json", + "files-scs:config/presets/pod-dynamic.json", + "files-scs:config/presets/representation-conversion.json", + "files-scs:config/presets/static.json", + "files-scs:config/presets/storage/backend/storage-memory.json", + "files-scs:config/presets/storage-wrapper.json", + "files-scs:config/presets/cli-params.json" + ], + "@graph": [ + { + "comment": "Main changes from default are the dynamic pods and the unsafe UnsecureWebIdExtractor", + "@id": "urn:solid-server:test:Instances", + "@type": "RecordObject", + "RecordObject:_record": [ + { + "RecordObject:_record_key": "initializer", + "RecordObject:_record_value": { + "@type": "SequenceHandler", + "SequenceHandler:_handlers": [ + { "@id": "urn:solid-server:default:RootContainerInitializer" }, + { "@id": "urn:solid-server:default:AclInitializer" } + ] + } + }, + { + "RecordObject:_record_key": "factory", + "RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" } + } + ] + }, + { + "@id": "urn:solid-server:default:LdpHandler", + "@type": "AuthenticatedLdpHandler", + "AuthenticatedLdpHandler:_args_requestParser": { + "@id": "urn:solid-server:default:RequestParser" + }, + "AuthenticatedLdpHandler:_args_credentialsExtractor": { + "@type": "WaterfallHandler", + "WaterfallHandler:_handlers": [ + { + "@type": "UnsecureWebIdExtractor" + }, + { + "@type": "EmptyCredentialsExtractor" + } + ] + }, + "AuthenticatedLdpHandler:_args_permissionsExtractor": { + "@id": "urn:solid-server:default:PermissionsExtractor" + }, + "AuthenticatedLdpHandler:_args_authorizer": { + "@id": "urn:solid-server:default:AclBasedAuthorizer" + }, + "AuthenticatedLdpHandler:_args_operationHandler": { + "@id": "urn:solid-server:default:OperationHandler" + }, + "AuthenticatedLdpHandler:_args_responseWriter": { + "@id": "urn:solid-server:default:ResponseWriter" + } + }, + { + "@id": "urn:solid-server:default:BaseUrlRouterRule", + "BaseUrlRouterRule:_baseStore": { + "@id": "urn:solid-server:default:MemoryResourceStore" + } + } + ] +}