CommunitySolidServer/test/unit/storage/IndexRepresentationstore.test.ts

137 lines
7.3 KiB
TypeScript

import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
import { IndexRepresentationStore } from '../../../src/storage/IndexRepresentationStore';
import type { ResourceStore } from '../../../src/storage/ResourceStore';
import { ConflictHttpError } from '../../../src/util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
import { readableToString } from '../../../src/util/StreamUtil';
describe('An IndexRepresentationStore', (): void => {
const baseUrl = 'http://test.com/';
const emptyContainer = { path: `${baseUrl}/container/` };
let source: jest.Mocked<ResourceStore>;
let store: IndexRepresentationStore;
beforeEach(async(): Promise<void> => {
source = {
getRepresentation: jest.fn((identifier: ResourceIdentifier): any => {
if (identifier.path === `${baseUrl}index.html`) {
return new BasicRepresentation('index data', identifier, 'text/html');
}
if (identifier.path.endsWith('/')) {
return new BasicRepresentation('container data', identifier, 'text/turtle');
}
throw new NotFoundHttpError();
}),
} as any;
store = new IndexRepresentationStore(source);
});
it('errors on invalid index names.', async(): Promise<void> => {
expect((): any => new IndexRepresentationStore(source, '../secretContainer/secret.key'))
.toThrow('Invalid index name');
});
it('retrieves the index resource if it is explicitly preferred.', async(): Promise<void> => {
const preferences = { type: { 'text/turtle': 0.5, 'text/html': 0.8 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('index data');
expect(source.getRepresentation).toHaveBeenCalledTimes(2);
expect(source.getRepresentation).toHaveBeenCalledWith({ path: `${baseUrl}index.html` }, preferences, undefined);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, {}, undefined);
// Use correct metadata
expect(result.metadata.identifier.value).toBe(baseUrl);
expect(result.metadata.contentType).toBe('text/html');
});
it('retrieves the index resource if there is a range preference.', async(): Promise<void> => {
const preferences = { type: { 'text/*': 0.8, 'other/other': 0.7 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('index data');
expect(source.getRepresentation).toHaveBeenCalledTimes(2);
expect(source.getRepresentation).toHaveBeenCalledWith({ path: `${baseUrl}index.html` }, preferences, undefined);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, {}, undefined);
// Use correct metadata
expect(result.metadata.identifier.value).toBe(baseUrl);
expect(result.metadata.contentType).toBe('text/html');
});
it('does not retrieve the index resource if there are no type preferences.', async(): Promise<void> => {
const preferences = {};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('container data');
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, preferences, undefined);
// Use correct metadata
expect(result.metadata.identifier.value).toBe(baseUrl);
expect(result.metadata.contentType).toBe('text/turtle');
});
it('does not retrieve the index resource on */*.', async(): Promise<void> => {
const preferences = { type: { '*/*': 1 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('container data');
});
it('errors if a non-404 error was thrown when accessing the index resource.', async(): Promise<void> => {
const preferences = { type: { 'text/turtle': 0.5, 'text/html': 0.8 }};
source.getRepresentation.mockRejectedValueOnce(new ConflictHttpError('conflict!'));
await expect(store.getRepresentation({ path: baseUrl }, preferences)).rejects.toThrow('conflict!');
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
});
it('requests the usual data if there is no index resource.', async(): Promise<void> => {
const preferences = { type: { 'text/turtle': 0.5, 'text/html': 0.8 }};
source.getRepresentation.mockRejectedValueOnce(new NotFoundHttpError());
const result = await store.getRepresentation(emptyContainer, preferences);
await expect(readableToString(result.data)).resolves.toBe('container data');
expect(source.getRepresentation).toHaveBeenCalledTimes(2);
expect(source.getRepresentation).toHaveBeenCalledWith(
{ path: `${emptyContainer.path}index.html` }, preferences, undefined,
);
expect(source.getRepresentation).toHaveBeenLastCalledWith(emptyContainer, preferences, undefined);
});
it('requests the usual data if the index media range is not the most preferred.', async(): Promise<void> => {
const preferences = { type: { 'text/turtle': 0.8, 'text/html': 0.5 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('container data');
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, preferences, undefined);
});
it('returns the index resource if the media range is set to */*.', async(): Promise<void> => {
store = new IndexRepresentationStore(source, 'base.html', '*/*');
// Mocking because we also change the index name
source.getRepresentation.mockResolvedValueOnce(new BasicRepresentation('index data', 'text/html'));
const preferences = { type: { 'text/turtle': 0.8, 'text/html': 0.5 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('index data');
expect(source.getRepresentation).toHaveBeenCalledTimes(2);
expect(source.getRepresentation).toHaveBeenCalledWith({ path: `${baseUrl}base.html` }, preferences, undefined);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, {}, undefined);
// Use correct metadata
expect(result.metadata.identifier.value).toBe(baseUrl);
expect(result.metadata.contentType).toBe('text/html');
});
it('returns the index resource if media range and Accept header are */*.', async(): Promise<void> => {
store = new IndexRepresentationStore(source, 'base.html', '*/*');
// Mocking because we also change the index name
source.getRepresentation.mockResolvedValueOnce(new BasicRepresentation('index data', 'text/html'));
const preferences = { type: { '*/*': 1 }};
const result = await store.getRepresentation({ path: baseUrl }, preferences);
await expect(readableToString(result.data)).resolves.toBe('index data');
expect(source.getRepresentation).toHaveBeenCalledTimes(2);
expect(source.getRepresentation).toHaveBeenCalledWith({ path: `${baseUrl}base.html` }, preferences, undefined);
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: baseUrl }, {}, undefined);
});
});