Skip to content
This repository has been archived by the owner on May 14, 2020. It is now read-only.

Uses Iri as NamedNode value and allow individual article retrieval #184

Merged
merged 41 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
55fd561
Change blanknodes to namednodes
nlisgo Jan 14, 2020
3d53e4c
refactor namedNode calls
nlisgo Jan 14, 2020
fbbb807
Create article with blankNode before namedNode is assigned
nlisgo Jan 14, 2020
41624dc
Remove console.log
nlisgo Jan 14, 2020
4455043
Add Iri as namedNode value and use in response location header
nlisgo Jan 16, 2020
f2bbb6f
Look up article if no route found
nlisgo Jan 16, 2020
29b9f07
Amend expected location header in add-article
nlisgo Jan 16, 2020
a19701f
Reverse change to location header of add article response
nlisgo Jan 17, 2020
98a12b0
Merge branch 'master' of github.com:libero/article-store into article…
nlisgo Jan 17, 2020
381aace
Reverse test change to location header of add article response
nlisgo Jan 17, 2020
7952d9f
Accept params and id for article endpoint
nlisgo Jan 17, 2020
7bb9fbd
Add tests for article end point
nlisgo Jan 17, 2020
62da38f
Adjust location header in add-article response
nlisgo Jan 17, 2020
4d408ff
Fix 404
nlisgo Jan 20, 2020
d96e8a5
Merge branch 'master' of github.com:libero/article-store into article…
nlisgo Jan 20, 2020
d21b19d
Reduce code in try block
nlisgo Jan 20, 2020
06f1e65
Remove specific article endpoint from router
nlisgo Jan 21, 2020
708d968
Merge branch 'master' of github.com:libero/article-store into article…
nlisgo Jan 21, 2020
dd0f6e9
cs
nlisgo Jan 21, 2020
74bbf95
Refactor add-article test to check new location header
nlisgo Jan 22, 2020
528ac26
Add article retrieval to middleware
nlisgo Jan 22, 2020
95acaa1
Throw with correct message
nlisgo Jan 22, 2020
8e7ec4b
Add url option to dummy request and make next optional
nlisgo Jan 22, 2020
df2865b
should only attempt article retrieval if next middleware throws not f…
nlisgo Jan 22, 2020
49ee33a
Merge branch 'master' of github.com:libero/article-store into article…
nlisgo Jan 22, 2020
0438de0
Use http-status-codes constants
nlisgo Jan 22, 2020
a0ab94e
should throw error raised in next middleware
nlisgo Jan 22, 2020
2805ffc
Address feedback
nlisgo Jan 22, 2020
e02077c
use AppContext in article request handler
nlisgo Jan 22, 2020
63254c4
No need to pass Articles to article request handler
nlisgo Jan 22, 2020
2f7caa1
Remove unneeded context option
nlisgo Jan 23, 2020
be71fdc
Simplify with use of AppMiddleware
nlisgo Jan 23, 2020
2174565
Avoid embedding try blocks
nlisgo Jan 23, 2020
55ec0d8
Merge branch 'master' into articles-dereferenceable-cont
thewilkybarkid Jan 23, 2020
0d43a9a
Try and compromise between the router and named nodes
thewilkybarkid Jan 23, 2020
5cbdaea
Use data factory from the context
thewilkybarkid Jan 23, 2020
fa7f1f4
Avoid shortcut
thewilkybarkid Jan 23, 2020
6ade9f7
Reduce work inside try block
thewilkybarkid Jan 23, 2020
701b790
Check the article is actually returned
thewilkybarkid Jan 23, 2020
8813756
Update Hypertest
thewilkybarkid Jan 23, 2020
222dc29
Merge branch 'master' into articles-dereferenceable-cont
thewilkybarkid Jan 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import errorHandler from './middleware/error-handler';
import jsonld from './middleware/jsonld';
import routing from './middleware/routing';
import namespaces from './namespaces';
import article from './routes/article';

export type AppState = DefaultState;

Expand Down Expand Up @@ -48,6 +49,7 @@ export default (
}));
app.use(apiDocumentationLink(apiDocumentationPath));
app.use(errorHandler());
app.use(article());
app.use(routing(router));

return app;
Expand Down
7 changes: 3 additions & 4 deletions src/routes/add-article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import uniqueString from 'unique-string';
import url from 'url';
import { AppContext, AppMiddleware } from '../app';
import { rdf, schema } from '../namespaces';
import Routes from './index';

export default (): AppMiddleware => (
async ({
articles, dataFactory: { namedNode, quad }, request, response, router,
articles, dataFactory: { namedNode, quad }, request, response,
}: AppContext, next: Next): Promise<void> => {
const id = clownface({ dataset: request.dataset }).has(rdf.type, schema.Article).term;

Expand All @@ -28,7 +27,7 @@ export default (): AppMiddleware => (
throw new createHttpError.BadRequest(`Article must have at least one ${termToString(schema('name'))}`);
}

const newId = namedNode(uniqueString());
const newId = namedNode(url.resolve(request.origin, 'articles/'.concat(uniqueString())));

[...request.dataset].forEach((originalQuad: Quad): void => {
let newQuad: Quad;
Expand All @@ -46,7 +45,7 @@ export default (): AppMiddleware => (
await articles.set(newId, request.dataset);

response.status = CREATED;
response.set('Location', url.resolve(request.origin, router.url(Routes.ArticleList)));
response.set('Location', newId.value);

await next();
}
Expand Down
35 changes: 35 additions & 0 deletions src/routes/article.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import createHttpError from 'http-errors';
import { OK } from 'http-status-codes';
import {
DefaultStateExtends, Middleware, Next,
} from 'koa';
import url from 'url';
import { AppContext } from '../app';
import { namedNode } from '../data-factory';
import ArticleNotFound from '../errors/article-not-found';

export default (): Middleware<DefaultStateExtends, AppContext> => (
nlisgo marked this conversation as resolved.
Show resolved Hide resolved
async ({
path, articles, request, response,
}: AppContext, next: Next): Promise<void> => {
try {
await next();
} catch (error) {
if (!(error instanceof createHttpError.NotFound)) {
throw error;
}

try {
nlisgo marked this conversation as resolved.
Show resolved Hide resolved
response.dataset = await articles.get(namedNode(url.resolve(request.origin, path)));
} catch (getError) {
if (getError instanceof ArticleNotFound) {
throw new createHttpError.NotFound(getError.message);
}

throw getError;
}

response.status = OK;
}
}
);
1 change: 1 addition & 0 deletions test/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Options = {
method?: string;
path?: string;
router?: Router;
url?: string;
nlisgo marked this conversation as resolved.
Show resolved Hide resolved
};

const dummyRouter = {
Expand Down
2 changes: 1 addition & 1 deletion test/routes/add-article.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ describe('add article', (): void => {
const response = await makeRequest(createArticle({ id, name }), undefined, articles);

expect(response.status).toBe(CREATED);
expect(response.get('Location')).toBe('http://example.com/path-to/article-list');
expect(await articles.count()).toBe(1);

const [newId, dataset] = (await all(articles))[0];

expect(response.get('Location')).toBe(newId.value);
expect(dataset.has(quad(newId, schema('name'), name))).toBe(true);
});

Expand Down
75 changes: 75 additions & 0 deletions test/routes/article.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
namedNode,
} from '@rdfjs/data-model';
import createHttpError from 'http-errors';
import { OK } from 'http-status-codes';
import { Response } from 'koa';
import InMemoryArticles from '../../src/adaptors/in-memory-articles';
import Articles from '../../src/articles';
import article from '../../src/routes/article';
import createContext from '../context';
import createArticle from '../create-article';
import runMiddleware, { NextMiddleware } from '../middleware';
import { WithDataset } from '../../src/middleware/dataset';

const dummyNext = async (): Promise<void> => {
throw new createHttpError.NotFound();
};

const makeRequest = async (
path: string,
articles?: Articles,
next?: NextMiddleware,
): Promise<WithDataset<Response>> => (
runMiddleware(article(), createContext({ articles, path }), typeof next !== 'undefined' ? next : dummyNext)
);

describe('article', (): void => {
it('should return a successful response', async (): Promise<void> => {
const id = namedNode('http://example.com/path-to/article/one');
const articles = new InMemoryArticles();
await articles.set(id, createArticle({ id }));

const response = await makeRequest('path-to/article/one', articles);

expect(response.status).toBe(OK);
});

it('should not attempt article retrieval when next middleware throws not found http error', async (): Promise<void> => {
const mockArticles: Articles = {
set: jest.fn(),
get: jest.fn(),
remove: jest.fn(),
contains: jest.fn(),
count: jest.fn(),
[Symbol.asyncIterator]: jest.fn(),
};

const next = jest.fn();
await makeRequest('path-to/article/one', mockArticles, next);
expect(mockArticles.get).toHaveBeenCalledTimes(0);
});

it('should throw an error if article is not found', async (): Promise<void> => {
const response = makeRequest('path-to/article/not-found');

await expect(response).rejects.toBeInstanceOf(createHttpError.NotFound);
await expect(response).rejects.toHaveProperty('message', 'Article http://example.com/path-to/article/not-found could not be found');
});

it('should throw error raised in next middleware', async (): Promise<void> => {
const next = async (): Promise<void> => {
throw new createHttpError.BadRequest();
};
const response = makeRequest('path-to/article/not-found', undefined, next);

await expect(response).rejects.toBeInstanceOf(createHttpError.BadRequest);
});

it('should call the next middleware', async (): Promise<void> => {
const next = jest.fn();
await makeRequest('path-to/article/one', undefined, next);

expect(next).toHaveBeenCalledTimes(1);
});
});