mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
chore: Remove assumption that DataAccessors have a root container by default
This commit is contained in:
parent
231349b30d
commit
36eed5d620
@ -27,7 +27,6 @@ import { guardStream } from '../../util/GuardedStream';
|
|||||||
import type { Guarded } from '../../util/GuardedStream';
|
import type { Guarded } from '../../util/GuardedStream';
|
||||||
import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy';
|
import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy';
|
||||||
import { isContainerIdentifier } from '../../util/PathUtil';
|
import { isContainerIdentifier } from '../../util/PathUtil';
|
||||||
import { generateResourceQuads } from '../../util/ResourceUtil';
|
|
||||||
import { CONTENT_TYPE, LDP } from '../../util/UriConstants';
|
import { CONTENT_TYPE, LDP } from '../../util/UriConstants';
|
||||||
import { toNamedNode } from '../../util/UriUtil';
|
import { toNamedNode } from '../../util/UriUtil';
|
||||||
import type { DataAccessor } from './DataAccessor';
|
import type { DataAccessor } from './DataAccessor';
|
||||||
@ -90,8 +89,7 @@ export class SparqlDataAccessor implements DataAccessor {
|
|||||||
const stream = await this.sendSparqlConstruct(query);
|
const stream = await this.sendSparqlConstruct(query);
|
||||||
const quads = await arrayifyStream(stream);
|
const quads = await arrayifyStream(stream);
|
||||||
|
|
||||||
// Root container will not have metadata if there are no containment triples
|
if (quads.length === 0) {
|
||||||
if (quads.length === 0 && !this.identifierStrategy.isRootContainer(identifier)) {
|
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,11 +98,6 @@ export class SparqlDataAccessor implements DataAccessor {
|
|||||||
metadata.contentType = INTERNAL_QUADS;
|
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;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import type { Server } from 'http';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as Path from 'path';
|
import * as Path from 'path';
|
||||||
import { Loader } from 'componentsjs';
|
import { Loader } from 'componentsjs';
|
||||||
|
import fetch from 'cross-fetch';
|
||||||
import type {
|
import type {
|
||||||
BodyParser,
|
BodyParser,
|
||||||
DataAccessor,
|
DataAccessor,
|
||||||
@ -24,6 +26,7 @@ import {
|
|||||||
ErrorResponseWriter,
|
ErrorResponseWriter,
|
||||||
GetOperationHandler,
|
GetOperationHandler,
|
||||||
HeadOperationHandler,
|
HeadOperationHandler,
|
||||||
|
HttpError,
|
||||||
InMemoryDataAccessor,
|
InMemoryDataAccessor,
|
||||||
LinkRelMetadataWriter,
|
LinkRelMetadataWriter,
|
||||||
LinkTypeParser,
|
LinkTypeParser,
|
||||||
@ -192,3 +195,21 @@ export const instantiateFromConfig = async(componentUrl: string, configFile: str
|
|||||||
const configPath = Path.join(__dirname, configFile);
|
const configPath = Path.join(__dirname, configFile);
|
||||||
return loader.instantiateFromUrl(componentUrl, configPath, undefined, { variables });
|
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<void> => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -3,14 +3,23 @@ import * as url from 'url';
|
|||||||
import { namedNode, quad } from '@rdfjs/data-model';
|
import { namedNode, quad } from '@rdfjs/data-model';
|
||||||
import { Parser } from 'n3';
|
import { Parser } from 'n3';
|
||||||
import type { MockResponse } from 'node-mocks-http';
|
import type { MockResponse } from 'node-mocks-http';
|
||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import { LDP } from '../../src/util/UriConstants';
|
import { LDP } from '../../src/util/UriConstants';
|
||||||
import { BasicConfig } from '../configs/BasicConfig';
|
import { BasicConfig } from '../configs/BasicConfig';
|
||||||
import { BasicHandlersConfig } from '../configs/BasicHandlersConfig';
|
import { BasicHandlersConfig } from '../configs/BasicHandlersConfig';
|
||||||
|
import { BASE } from '../configs/Util';
|
||||||
import { call } from '../util/Util';
|
import { call } from '../util/Util';
|
||||||
|
|
||||||
describe('An integrated AuthenticatedLdpHandler', (): void => {
|
describe('An integrated AuthenticatedLdpHandler', (): void => {
|
||||||
describe('with simple handlers', (): void => {
|
describe('with simple handlers', (): void => {
|
||||||
const handler = new BasicConfig().getHttpHandler();
|
const config = new BasicConfig();
|
||||||
|
const handler = config.getHttpHandler();
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// 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<void> => {
|
it('can add, read and delete data based on incoming requests.', async(): Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
@ -61,7 +70,14 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with simple PATCH handlers', (): void => {
|
describe('with simple PATCH handlers', (): void => {
|
||||||
const handler = new BasicHandlersConfig().getHttpHandler();
|
const config = new BasicHandlersConfig();
|
||||||
|
const handler = config.getHttpHandler();
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, config.store);
|
||||||
|
await initializer.handleSafe();
|
||||||
|
});
|
||||||
|
|
||||||
it('can handle simple SPARQL updates.', async(): Promise<void> => {
|
it('can handle simple SPARQL updates.', async(): Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
@ -126,7 +142,14 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with simple PUT handlers', (): void => {
|
describe('with simple PUT handlers', (): void => {
|
||||||
const handler = new BasicHandlersConfig().getHttpHandler();
|
const config = new BasicHandlersConfig();
|
||||||
|
const handler = config.getHttpHandler();
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, config.store);
|
||||||
|
await initializer.handleSafe();
|
||||||
|
});
|
||||||
|
|
||||||
it('should overwrite the content on PUT request.', async(): Promise<void> => {
|
it('should overwrite the content on PUT request.', async(): Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import type { MockResponse } from 'node-mocks-http';
|
import type { MockResponse } from 'node-mocks-http';
|
||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import { BasicHandlersWithAclConfig } from '../configs/BasicHandlersWithAclConfig';
|
import { BasicHandlersWithAclConfig } from '../configs/BasicHandlersWithAclConfig';
|
||||||
|
import { BASE } from '../configs/Util';
|
||||||
import { AclTestHelper } from '../util/TestHelpers';
|
import { AclTestHelper } from '../util/TestHelpers';
|
||||||
import { call } from '../util/Util';
|
import { call } from '../util/Util';
|
||||||
|
|
||||||
@ -9,6 +11,12 @@ describe('A server with authorization', (): void => {
|
|||||||
const { store } = config;
|
const { store } = config;
|
||||||
const aclHelper = new AclTestHelper(store, 'http://test.com/');
|
const aclHelper = new AclTestHelper(store, 'http://test.com/');
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, config.store);
|
||||||
|
await initializer.handleSafe();
|
||||||
|
});
|
||||||
|
|
||||||
it('can create new entries.', async(): Promise<void> => {
|
it('can create new entries.', async(): Promise<void> => {
|
||||||
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
|
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createReadStream, mkdirSync } from 'fs';
|
import { createReadStream, mkdirSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as rimraf from 'rimraf';
|
import * as rimraf from 'rimraf';
|
||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
|
||||||
import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor';
|
import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor';
|
||||||
import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor';
|
import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor';
|
||||||
@ -40,6 +41,10 @@ describe.each([ dataAccessorStore, inMemoryDataAccessorStore ])('A server using
|
|||||||
// Make sure the root directory exists
|
// Make sure the root directory exists
|
||||||
mkdirSync(rootFilePath, { recursive: true });
|
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
|
// Use store instead of file access so tests also work for non-file backends
|
||||||
await config.store.setRepresentation({ path: `${BASE}/permanent.txt` }, {
|
await config.store.setRepresentation({ path: `${BASE}/permanent.txt` }, {
|
||||||
binary: true,
|
binary: true,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { mkdirSync } from 'fs';
|
import { mkdirSync } from 'fs';
|
||||||
import * as rimraf from 'rimraf';
|
import * as rimraf from 'rimraf';
|
||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import type { HttpHandler } from '../../src/server/HttpHandler';
|
import type { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor';
|
import { FileDataAccessor } from '../../src/storage/accessors/FileDataAccessor';
|
||||||
import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor';
|
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);
|
config = configFn(rootFilePath);
|
||||||
handler = config.getHttpHandler();
|
handler = config.getHttpHandler();
|
||||||
fileHelper = new FileTestHelper(handler, new URL(BASE));
|
fileHelper = new FileTestHelper(handler, new URL(BASE));
|
||||||
|
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, config.store);
|
||||||
|
await initializer.handleSafe();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import streamifyArray from 'streamify-array';
|
import streamifyArray from 'streamify-array';
|
||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import type { Representation } from '../../src/ldp/representation/Representation';
|
import type { Representation } from '../../src/ldp/representation/Representation';
|
||||||
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
|
||||||
import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor';
|
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 { WrappedExpiringResourceLocker } from '../../src/util/locking/WrappedExpiringResourceLocker';
|
||||||
import { guardedStreamFrom } from '../../src/util/StreamUtil';
|
import { guardedStreamFrom } from '../../src/util/StreamUtil';
|
||||||
import { CONTENT_TYPE } from '../../src/util/UriConstants';
|
import { CONTENT_TYPE } from '../../src/util/UriConstants';
|
||||||
|
import { BASE } from '../configs/Util';
|
||||||
|
|
||||||
describe('A LockingResourceStore', (): void => {
|
describe('A LockingResourceStore', (): void => {
|
||||||
let path: string;
|
let path: string;
|
||||||
@ -28,6 +30,10 @@ describe('A LockingResourceStore', (): void => {
|
|||||||
path = `${base}path`;
|
path = `${base}path`;
|
||||||
source = new DataAccessorBasedStore(new InMemoryDataAccessor(base), new SingleRootIdentifierStrategy(base));
|
source = new DataAccessorBasedStore(new InMemoryDataAccessor(base), new SingleRootIdentifierStrategy(base));
|
||||||
|
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, source);
|
||||||
|
await initializer.handleSafe();
|
||||||
|
|
||||||
locker = new SingleThreadedResourceLocker();
|
locker = new SingleThreadedResourceLocker();
|
||||||
expiringLocker = new WrappedExpiringResourceLocker(locker, 1000);
|
expiringLocker = new WrappedExpiringResourceLocker(locker, 1000);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { join } from 'path';
|
|||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
|
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
|
||||||
import { readableToString } from '../../src/util/StreamUtil';
|
import { readableToString } from '../../src/util/StreamUtil';
|
||||||
import { instantiateFromConfig } from '../configs/Util';
|
import { initServerStore, instantiateFromConfig } from '../configs/Util';
|
||||||
|
|
||||||
const port = 6003;
|
const port = 6003;
|
||||||
const baseUrl = `http://localhost:${port}/`;
|
const baseUrl = `http://localhost:${port}/`;
|
||||||
@ -21,6 +21,7 @@ describe('A server with a pod handler', (): void => {
|
|||||||
},
|
},
|
||||||
) as HttpServerFactory;
|
) as HttpServerFactory;
|
||||||
server = factory.startServer(port);
|
server = factory.startServer(port);
|
||||||
|
await initServerStore(server, baseUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
|
||||||
import { SparqlDataAccessor } from '../../src/storage/accessors/SparqlDataAccessor';
|
import { SparqlDataAccessor } from '../../src/storage/accessors/SparqlDataAccessor';
|
||||||
import { INTERNAL_QUADS } from '../../src/util/ContentTypes';
|
import { INTERNAL_QUADS } from '../../src/util/ContentTypes';
|
||||||
import { SingleRootIdentifierStrategy } from '../../src/util/identifiers/SingleRootIdentifierStrategy';
|
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 handler = config.getHttpHandler();
|
||||||
const fileHelper = new FileTestHelper(handler, new URL(BASE));
|
const fileHelper = new FileTestHelper(handler, new URL(BASE));
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// Initialize store
|
||||||
|
const initializer = new RootContainerInitializer(BASE, config.store);
|
||||||
|
await initializer.handleSafe();
|
||||||
|
});
|
||||||
|
|
||||||
it('can add a Turtle file to the store.', async():
|
it('can add a Turtle file to the store.', async():
|
||||||
Promise<void> => {
|
Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
|
@ -2,7 +2,7 @@ import type { Server } from 'http';
|
|||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
|
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
|
||||||
import { instantiateFromConfig } from '../configs/Util';
|
import { initServerStore, instantiateFromConfig } from '../configs/Util';
|
||||||
|
|
||||||
const port = 6001;
|
const port = 6001;
|
||||||
const serverUrl = `http://localhost:${port}/`;
|
const serverUrl = `http://localhost:${port}/`;
|
||||||
@ -20,6 +20,7 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => {
|
|||||||
},
|
},
|
||||||
) as HttpServerFactory;
|
) as HttpServerFactory;
|
||||||
server = factory.startServer(port);
|
server = factory.startServer(port);
|
||||||
|
await initServerStore(server, serverUrl, headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
|
@ -18,6 +18,9 @@ describe('An InMemoryDataAccessor', (): void => {
|
|||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
accessor = new InMemoryDataAccessor(base);
|
accessor = new InMemoryDataAccessor(base);
|
||||||
|
|
||||||
|
// Create default root container
|
||||||
|
await accessor.writeContainer({ path: `${base}` }, new RepresentationMetadata());
|
||||||
|
|
||||||
metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_OCTET_STREAM });
|
metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_OCTET_STREAM });
|
||||||
|
|
||||||
data = guardedStreamFrom([ 'data' ]);
|
data = guardedStreamFrom([ 'data' ]);
|
||||||
|
@ -121,26 +121,6 @@ describe('A SparqlDataAccessor', (): void => {
|
|||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates resource metadata for the root container.', async(): Promise<void> => {
|
|
||||||
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 <meta:${base}> { ?s ?p ?o. } }`,
|
|
||||||
'}',
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws 404 if no metadata was found.', async(): Promise<void> => {
|
it('throws 404 if no metadata was found.', async(): Promise<void> => {
|
||||||
// Clear triples array
|
// Clear triples array
|
||||||
triples = [];
|
triples = [];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user