diff --git a/src/storage/accessors/SparqlDataAccessor.ts b/src/storage/accessors/SparqlDataAccessor.ts index 3bb9f13fe..0b3b60f12 100644 --- a/src/storage/accessors/SparqlDataAccessor.ts +++ b/src/storage/accessors/SparqlDataAccessor.ts @@ -27,7 +27,6 @@ import { guardStream } from '../../util/GuardedStream'; import type { Guarded } from '../../util/GuardedStream'; import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy'; import { isContainerIdentifier } from '../../util/PathUtil'; -import { generateResourceQuads } from '../../util/ResourceUtil'; import { CONTENT_TYPE, LDP } from '../../util/UriConstants'; import { toNamedNode } from '../../util/UriUtil'; import type { DataAccessor } from './DataAccessor'; @@ -90,8 +89,7 @@ export class SparqlDataAccessor implements DataAccessor { const stream = await this.sendSparqlConstruct(query); const quads = await arrayifyStream(stream); - // Root container will not have metadata if there are no containment triples - if (quads.length === 0 && !this.identifierStrategy.isRootContainer(identifier)) { + if (quads.length === 0) { throw new NotFoundHttpError(); } @@ -100,11 +98,6 @@ export class SparqlDataAccessor implements DataAccessor { metadata.contentType = INTERNAL_QUADS; } - // Need to generate type metadata for the root container since it's not stored - if (this.identifierStrategy.isRootContainer(identifier)) { - metadata.addQuads(generateResourceQuads(name, true)); - } - return metadata; } diff --git a/test/configs/Util.ts b/test/configs/Util.ts index a39f87fe1..13a7b2589 100644 --- a/test/configs/Util.ts +++ b/test/configs/Util.ts @@ -1,6 +1,8 @@ +import type { Server } from 'http'; import { join } from 'path'; import * as Path from 'path'; import { Loader } from 'componentsjs'; +import fetch from 'cross-fetch'; import type { BodyParser, DataAccessor, @@ -24,6 +26,7 @@ import { ErrorResponseWriter, GetOperationHandler, HeadOperationHandler, + HttpError, InMemoryDataAccessor, LinkRelMetadataWriter, LinkTypeParser, @@ -192,3 +195,21 @@ export const instantiateFromConfig = async(componentUrl: string, configFile: str const configPath = Path.join(__dirname, configFile); return loader.instantiateFromUrl(componentUrl, configPath, undefined, { variables }); }; + +/** + * Initializes the root container of the server. + * Useful for when the RootContainerInitializer was not instantiated. + */ +export const initServerStore = async(server: Server, baseUrl: string, headers: HeadersInit = {}): Promise => { + const res = await fetch(baseUrl, { + method: 'PUT', + headers: { + ...headers, + 'content-type': 'text/turtle', + }, + body: '', + }); + if (res.status >= 400) { + throw new HttpError(res.status, 'Error', res.statusText); + } +}; diff --git a/test/integration/AuthenticatedLdpHandler.test.ts b/test/integration/AuthenticatedLdpHandler.test.ts index 2723dcddb..f80f8c07e 100644 --- a/test/integration/AuthenticatedLdpHandler.test.ts +++ b/test/integration/AuthenticatedLdpHandler.test.ts @@ -3,14 +3,23 @@ import * as url from 'url'; import { namedNode, quad } from '@rdfjs/data-model'; import { Parser } from 'n3'; import type { MockResponse } from 'node-mocks-http'; +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import { LDP } from '../../src/util/UriConstants'; import { BasicConfig } from '../configs/BasicConfig'; import { BasicHandlersConfig } from '../configs/BasicHandlersConfig'; +import { BASE } from '../configs/Util'; import { call } from '../util/Util'; describe('An integrated AuthenticatedLdpHandler', (): void => { describe('with simple handlers', (): void => { - const handler = new BasicConfig().getHttpHandler(); + const config = new BasicConfig(); + const handler = config.getHttpHandler(); + + beforeAll(async(): Promise => { + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + }); it('can add, read and delete data based on incoming requests.', async(): Promise => { // POST @@ -61,7 +70,14 @@ describe('An integrated AuthenticatedLdpHandler', (): void => { }); describe('with simple PATCH handlers', (): void => { - const handler = new BasicHandlersConfig().getHttpHandler(); + const config = new BasicHandlersConfig(); + const handler = config.getHttpHandler(); + + beforeAll(async(): Promise => { + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + }); it('can handle simple SPARQL updates.', async(): Promise => { // POST @@ -126,7 +142,14 @@ describe('An integrated AuthenticatedLdpHandler', (): void => { }); describe('with simple PUT handlers', (): void => { - const handler = new BasicHandlersConfig().getHttpHandler(); + const config = new BasicHandlersConfig(); + const handler = config.getHttpHandler(); + + beforeAll(async(): Promise => { + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + }); it('should overwrite the content on PUT request.', async(): Promise => { // POST diff --git a/test/integration/Authorization.test.ts b/test/integration/Authorization.test.ts index 480179a12..65019d85f 100644 --- a/test/integration/Authorization.test.ts +++ b/test/integration/Authorization.test.ts @@ -1,5 +1,7 @@ import type { MockResponse } from 'node-mocks-http'; +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import { BasicHandlersWithAclConfig } from '../configs/BasicHandlersWithAclConfig'; +import { BASE } from '../configs/Util'; import { AclTestHelper } from '../util/TestHelpers'; import { call } from '../util/Util'; @@ -9,6 +11,12 @@ describe('A server with authorization', (): void => { const { store } = config; const aclHelper = new AclTestHelper(store, 'http://test.com/'); + beforeAll(async(): Promise => { + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + }); + it('can create new entries.', async(): Promise => { await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent'); diff --git a/test/integration/FullConfig.acl.test.ts b/test/integration/FullConfig.acl.test.ts index 9a747a568..89e73846b 100644 --- a/test/integration/FullConfig.acl.test.ts +++ b/test/integration/FullConfig.acl.test.ts @@ -1,6 +1,7 @@ import { createReadStream, mkdirSync } from 'fs'; import { join } from 'path'; import * as rimraf from 'rimraf'; +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata'; import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor'; import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor'; @@ -40,6 +41,10 @@ describe.each([ dataAccessorStore, inMemoryDataAccessorStore ])('A server using // Make sure the root directory exists mkdirSync(rootFilePath, { recursive: true }); + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + // Use store instead of file access so tests also work for non-file backends await config.store.setRepresentation({ path: `${BASE}/permanent.txt` }, { binary: true, diff --git a/test/integration/FullConfig.noAuth.test.ts b/test/integration/FullConfig.noAuth.test.ts index e0a912368..4b83a4ba1 100644 --- a/test/integration/FullConfig.noAuth.test.ts +++ b/test/integration/FullConfig.noAuth.test.ts @@ -1,5 +1,6 @@ import { mkdirSync } from 'fs'; import * as rimraf from 'rimraf'; +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import type { HttpHandler } from '../../src/server/HttpHandler'; import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor'; import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor'; @@ -35,6 +36,10 @@ describe.each(configs)('A server using a %s', (name, configFn): void => { config = configFn(rootFilePath); handler = config.getHttpHandler(); fileHelper = new FileTestHelper(handler, new URL(BASE)); + + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); }); afterAll(async(): Promise => { diff --git a/test/integration/LockingResourceStore.test.ts b/test/integration/LockingResourceStore.test.ts index 3c04fd9a3..f60ae002e 100644 --- a/test/integration/LockingResourceStore.test.ts +++ b/test/integration/LockingResourceStore.test.ts @@ -1,4 +1,5 @@ import streamifyArray from 'streamify-array'; +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import type { Representation } from '../../src/ldp/representation/Representation'; import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata'; import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor'; @@ -13,6 +14,7 @@ import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThrea import { WrappedExpiringResourceLocker } from '../../src/util/locking/WrappedExpiringResourceLocker'; import { guardedStreamFrom } from '../../src/util/StreamUtil'; import { CONTENT_TYPE } from '../../src/util/UriConstants'; +import { BASE } from '../configs/Util'; describe('A LockingResourceStore', (): void => { let path: string; @@ -28,6 +30,10 @@ describe('A LockingResourceStore', (): void => { path = `${base}path`; source = new DataAccessorBasedStore(new InMemoryDataAccessor(base), new SingleRootIdentifierStrategy(base)); + // Initialize store + const initializer = new RootContainerInitializer(BASE, source); + await initializer.handleSafe(); + locker = new SingleThreadedResourceLocker(); expiringLocker = new WrappedExpiringResourceLocker(locker, 1000); diff --git a/test/integration/PodCreation.test.ts b/test/integration/PodCreation.test.ts index f6737f384..9250d1008 100644 --- a/test/integration/PodCreation.test.ts +++ b/test/integration/PodCreation.test.ts @@ -3,7 +3,7 @@ import { join } from 'path'; import fetch from 'cross-fetch'; import type { HttpServerFactory } from '../../src/server/HttpServerFactory'; import { readableToString } from '../../src/util/StreamUtil'; -import { instantiateFromConfig } from '../configs/Util'; +import { initServerStore, instantiateFromConfig } from '../configs/Util'; const port = 6003; const baseUrl = `http://localhost:${port}/`; @@ -21,6 +21,7 @@ describe('A server with a pod handler', (): void => { }, ) as HttpServerFactory; server = factory.startServer(port); + await initServerStore(server, baseUrl); }); afterAll(async(): Promise => { diff --git a/test/integration/SparqlStorage.test.ts b/test/integration/SparqlStorage.test.ts index e161cfad7..c3dc69b46 100644 --- a/test/integration/SparqlStorage.test.ts +++ b/test/integration/SparqlStorage.test.ts @@ -1,3 +1,4 @@ +import { RootContainerInitializer } from '../../src/init/RootContainerInitializer'; import { SparqlDataAccessor } from '../../src/storage/accessors/SparqlDataAccessor'; import { INTERNAL_QUADS } from '../../src/util/ContentTypes'; import { SingleRootIdentifierStrategy } from '../../src/util/identifiers/SingleRootIdentifierStrategy'; @@ -13,6 +14,12 @@ describeIf('docker', 'a server with a SPARQL endpoint as storage', (): void => { const handler = config.getHttpHandler(); const fileHelper = new FileTestHelper(handler, new URL(BASE)); + beforeAll(async(): Promise => { + // Initialize store + const initializer = new RootContainerInitializer(BASE, config.store); + await initializer.handleSafe(); + }); + it('can add a Turtle file to the store.', async(): Promise => { // POST diff --git a/test/integration/WebSocketsProtocol.test.ts b/test/integration/WebSocketsProtocol.test.ts index a118c9ce1..83132bc6e 100644 --- a/test/integration/WebSocketsProtocol.test.ts +++ b/test/integration/WebSocketsProtocol.test.ts @@ -2,7 +2,7 @@ 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'; +import { initServerStore, instantiateFromConfig } from '../configs/Util'; const port = 6001; const serverUrl = `http://localhost:${port}/`; @@ -20,6 +20,7 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => { }, ) as HttpServerFactory; server = factory.startServer(port); + await initServerStore(server, serverUrl, headers); }); afterAll(async(): Promise => { diff --git a/test/unit/storage/accessors/InMemoryDataAccessor.test.ts b/test/unit/storage/accessors/InMemoryDataAccessor.test.ts index 8c61102d9..a83bafec1 100644 --- a/test/unit/storage/accessors/InMemoryDataAccessor.test.ts +++ b/test/unit/storage/accessors/InMemoryDataAccessor.test.ts @@ -18,6 +18,9 @@ describe('An InMemoryDataAccessor', (): void => { beforeEach(async(): Promise => { accessor = new InMemoryDataAccessor(base); + // Create default root container + await accessor.writeContainer({ path: `${base}` }, new RepresentationMetadata()); + metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_OCTET_STREAM }); data = guardedStreamFrom([ 'data' ]); diff --git a/test/unit/storage/accessors/SparqlDataAccessor.test.ts b/test/unit/storage/accessors/SparqlDataAccessor.test.ts index 4e4f3f8bb..0d838dbc1 100644 --- a/test/unit/storage/accessors/SparqlDataAccessor.test.ts +++ b/test/unit/storage/accessors/SparqlDataAccessor.test.ts @@ -121,26 +121,6 @@ describe('A SparqlDataAccessor', (): void => { ])); }); - it('generates resource metadata for the root container.', async(): Promise => { - metadata = await accessor.getMetadata({ path: base }); - expect(metadata.quads()).toBeRdfIsomorphic([ - quad(namedNode('this'), namedNode('a'), namedNode('triple')), - quad(namedNode(base), toNamedNode(RDF.type), toNamedNode(LDP.Container)), - quad(namedNode(base), toNamedNode(RDF.type), toNamedNode(LDP.BasicContainer)), - quad(namedNode(base), toNamedNode(RDF.type), toNamedNode(LDP.Resource)), - ]); - - expect(fetchTriples).toHaveBeenCalledTimes(1); - expect(fetchTriples.mock.calls[0][0]).toBe(endpoint); - expect(simplifyQuery(fetchTriples.mock.calls[0][1])).toBe(simplifyQuery([ - 'CONSTRUCT { ?s ?p ?o. } WHERE {', - ` { GRAPH <${base}> { ?s ?p ?o. } }`, - ' UNION', - ` { GRAPH { ?s ?p ?o. } }`, - '}', - ])); - }); - it('throws 404 if no metadata was found.', async(): Promise => { // Clear triples array triples = [];