Skip to content

Commit

Permalink
Merge pull request #18 from vaimee/14-update-operations-do-not-update…
Browse files Browse the repository at this point in the history
…-the-td-id-correctly

Update operations do not update the td id correctly
  • Loading branch information
relu91 authored Sep 6, 2022
2 parents ce6a476 + 2dc14bb commit 4513dd9
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 4 deletions.
15 changes: 12 additions & 3 deletions src/api-reference/things/things.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Injectable } from '@nestjs/common';
import { generate as generatePatch, apply as mergePatch } from 'json-merge-patch';

import {
DuplicateIdException,
InvalidThingDescriptionException,
MismatchIdException,
NonAnonymousThingDescription,
Expand Down Expand Up @@ -64,15 +65,16 @@ export class ThingsService {
const now = new Date().toISOString();
const internalThingDescription = await this.thingDescriptionRepository.findFirst({ where: { urn: id } });
if (internalThingDescription) {
await this.assertUniqueThingDescriptionId(internalThingDescription.urn, dto.id);
await this.thingDescriptionRepository.update({
where: { id: internalThingDescription.id },
data: { json: dto, modified: now },
data: { json: dto, urn: dto.id, modified: now },
});
const patch = generatePatch(internalThingDescription.json, dto) ?? {};
this.eventsService.emitUpdated({ id, ...patch });
return true;
} else {
if ( id !== dto.id) throw new MismatchIdException();
if (id !== dto.id) throw new MismatchIdException();
await this.thingDescriptionRepository.create({
urn: id,
json: dto,
Expand All @@ -93,11 +95,12 @@ export class ThingsService {

const patchedThingDescription = mergePatch(internalThingDescription.json, dto);
this.requireValidThingDescription(patchedThingDescription);
await this.assertUniqueThingDescriptionId(internalThingDescription.urn, patchedThingDescription.id);
const now = new Date().toISOString();

await this.thingDescriptionRepository.update({
where: { id: internalThingDescription.id },
data: { json: patchedThingDescription, modified: now },
data: { json: patchedThingDescription, urn: patchedThingDescription.id, modified: now },
});

this.eventsService.emitUpdated({ id: internalThingDescription.urn, ...dto });
Expand Down Expand Up @@ -127,4 +130,10 @@ export class ThingsService {
throw new InvalidThingDescriptionException(errors);
}
}

private async assertUniqueThingDescriptionId(currentId: string, newId: string | undefined): Promise<void> {
if (!newId || newId === currentId) return;
const idExist = await this.thingDescriptionRepository.exist({ where: { urn: newId } });
if (idExist) throw new DuplicateIdException(newId as string);
}
}
12 changes: 12 additions & 0 deletions src/common/exceptions/duplicate-id.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProblemDetailsException } from './problem-details.exception';

export class DuplicateIdException extends ProblemDetailsException {
public constructor(id: string) {
super({
type: '/errors/types/duplicate-id',
title: 'Duplicate Id',
status: 409,
detail: `The id ${id} is already in use by another Thing Description`,
});
}
}
1 change: 1 addition & 0 deletions src/common/exceptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './invalid-credentials.exception';
export * from './invalid-thing-description.exception';
export * from './invalid-token.exception';
export * from './non-anonymous-thing-description.exception';
export * from './duplicate-id.exception';
export * from './mismatch-id.exception';
89 changes: 88 additions & 1 deletion test/e2e/things.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,31 @@ describe('/things', () => {
});
});

it('should fail to update the Thing Description id if the id is already exist', async () => {
const thingDescriptions = [];
const id = `${validThingDescription.id}:${getShortUnique()}`;
for (let i = 0; i < 2; i++) {
const validThingDescriptionObject = validThingDescription;
validThingDescriptionObject.id = `${id}:${i}`;
thingDescriptions.push(validThingDescriptionObject);
const response = await axios.put(`/things/${validThingDescriptionObject.id}`, validThingDescriptionObject, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});
expect(response.status).toBe(201);
}
const { status, data } = await axios.put(`/things/${id}:0`, thingDescriptions[1], {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});

expect(status).toBe(409);
expect(data).toMatchObject({
type: '/errors/types/duplicate-id',
title: 'Duplicate Id',
status: 409,
detail: `The id ${validThingDescription.id} is already in use by another Thing Description`,
});
});

it('should update the Thing Description', async () => {
const { headers } = await axios.post('/things', validAnonymousThingDescription, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
Expand All @@ -270,11 +295,28 @@ describe('/things', () => {
});
});

it('should update the Thing Description id and update the URL urn', async () => {
const { headers } = await axios.post('/things', validAnonymousThingDescription, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});
const id = `urn:dev:ops:${getShortUnique()}`;
const updatedThingDescription = { ...validAnonymousThingDescription, id: id };

await axios.put(headers.location, updatedThingDescription, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});

const { status } = await axios.get(headers.location);
expect(status).toBe(404);

const { data } = await axios.get(`/things/${id}`);
expect(data).toMatchObject(updatedThingDescription);
});

it('should create the Thing Description', async () => {
const id = `${validThingDescription.id}:${getShortUnique()}`;
const validThingDescriptionObject = validThingDescription;
validThingDescriptionObject.id = id;
console.log(validThingDescriptionObject.id);
const { status } = await axios.put(`/things/${id}`, validThingDescriptionObject, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});
Expand Down Expand Up @@ -323,6 +365,31 @@ describe('/things', () => {
});
});

it('should fail to update the Thing Description id if the id is already exist', async () => {
const thingDescriptions = [];
const id = `${validThingDescription.id}:${getShortUnique()}`;
for (let i = 0; i < 2; i++) {
const validThingDescriptionObject = validThingDescription;
validThingDescriptionObject.id = `${id}:${i}`;
thingDescriptions.push(validThingDescriptionObject);
const response = await axios.put(`/things/${validThingDescriptionObject.id}`, validThingDescriptionObject, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});
expect(response.status).toBe(201);
}
const { status, data } = await axios.patch(`/things/${id}:0`, thingDescriptions[1], {
headers: { Authorization: `Bearer ${defaultAccessToken}`, 'Content-Type': 'application/merge-patch+json' },
});

expect(status).toBe(409);
expect(data).toMatchObject({
type: '/errors/types/duplicate-id',
title: 'Duplicate Id',
status: 409,
detail: `The id ${validThingDescription.id} is already in use by another Thing Description`,
});
});

it('should fail to update the Thing Description when the patched Thing Description is invalid', async () => {
const { headers } = await axios.post('/things', validAnonymousThingDescription, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
Expand Down Expand Up @@ -366,6 +433,26 @@ describe('/things', () => {
...modifiedParts,
});
});

it('should update the Thing Description id and update the URL urn', async () => {
const { headers } = await axios.post('/things', validAnonymousThingDescription, {
headers: { Authorization: `Bearer ${defaultAccessToken}` },
});
const modifiedId = { id: `urn:dev:ops:${getShortUnique()}` };

await axios.patch(headers.location, modifiedId, {
headers: { Authorization: `Bearer ${defaultAccessToken}`, 'Content-Type': 'application/merge-patch+json' },
});

const { status } = await axios.get(headers.location);
expect(status).toBe(404);

const { data } = await axios.get(`/things/${modifiedId.id}`);
expect(data).toStrictEqual({
...validAnonymousThingDescription,
...modifiedId,
});
});
});

describe('DELETE', () => {
Expand Down

0 comments on commit 4513dd9

Please sign in to comment.