import { BasicRepresentation } from '../../../src/http/representation/BasicRepresentation'; import type { ResourceIdentifier } from '../../../src/http/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; let store: IndexRepresentationStore; beforeEach(async(): Promise => { 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 => { expect((): any => new IndexRepresentationStore(source, '../secretContainer/secret.key')) .toThrow('Invalid index name'); }); it('retrieves the index resource if it is explicitly preferred.', async(): Promise => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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); }); });