From 8af79e76128912a4a4cd82d6bdcb8d5e02185bd2 Mon Sep 17 00:00:00 2001 From: Alexei Yashkov Date: Sun, 22 Oct 2023 12:31:32 -0400 Subject: [PATCH] Story #8: implement canDelete property --- src/lib/accessor.spec.ts | 69 ++++++++++++++++++++++++++++++++++++++ src/lib/collection.spec.ts | 69 ++++++++++++++++++++++++++++++++++++++ src/lib/hal-base.ts | 27 +++++++++++---- src/lib/resource.spec.ts | 69 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 7 deletions(-) diff --git a/src/lib/accessor.spec.ts b/src/lib/accessor.spec.ts index c66eb30..700cd75 100644 --- a/src/lib/accessor.spec.ts +++ b/src/lib/accessor.spec.ts @@ -443,6 +443,75 @@ describe('Accessor', () => { }); }); + describe('#canDelete', () => { + it('is true when no methods array', () => { + const noMethods = new Accessor({ + _client: spectator.httpClient, + _links: { + self: { href: '/api/v1/items' } + } + }); + + expect(noMethods.canDelete).toBe(true); + }); + + it('is true when methods is not an array', () => { + const notArray = new Accessor({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: 'GET' + } + } + }); + + expect(notArray.canDelete).toBe(true); + }); + + it('is false when no POST in methods array', () => { + const noPost = new Accessor({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET'] + } + } + }); + + expect(noPost.canDelete).toBe(false); + }); + + it('is true when DELETE is in methods array', () => { + const withPost = new Accessor({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET', 'DELETE', 'PUT'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + + it('is true when DELETE matches case insensitively', () => { + const withPost = new Accessor({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: [null, 'DeLeTe'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + }); + describe('#delete()', () => { it('deletes resource at self link', () => { let next = false; diff --git a/src/lib/collection.spec.ts b/src/lib/collection.spec.ts index aa02d97..16dfcbd 100644 --- a/src/lib/collection.spec.ts +++ b/src/lib/collection.spec.ts @@ -651,6 +651,75 @@ describe('Collection', () => { }); }); + describe('#canDelete', () => { + it('is true when no methods array', () => { + const noMethods = new Collection(TestResource, { + _client: spectator.httpClient, + _links: { + self: { href: '/api/v1/items' } + } + }); + + expect(noMethods.canDelete).toBe(true); + }); + + it('is true when methods is not an array', () => { + const notArray = new Collection(TestResource, { + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: 'GET' + } + } + }); + + expect(notArray.canDelete).toBe(true); + }); + + it('is false when no POST in methods array', () => { + const noPost = new Collection(TestResource, { + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET'] + } + } + }); + + expect(noPost.canDelete).toBe(false); + }); + + it('is true when DELETE is in methods array', () => { + const withPost = new Collection(TestResource, { + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET', 'DELETE', 'PUT'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + + it('is true when DELETE matches case insensitively', () => { + const withPost = new Collection(TestResource, { + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: [null, 'DeLeTe'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + }); + describe('#delete()', () => { it('deletes resource at self link when it exists', () => { let next = false; diff --git a/src/lib/hal-base.ts b/src/lib/hal-base.ts index e3f5ba1..b6c7415 100644 --- a/src/lib/hal-base.ts +++ b/src/lib/hal-base.ts @@ -34,16 +34,19 @@ export abstract class HalBase { /** * This property is true when either `methods` array does not exist - * in the `self` link or the array exists and contains `POST` method. + * in the `self` link or the array exists and contains `POST` string. */ get canCreate(): boolean { - const methods = this._links[self]?.methods; - - if (!Array.isArray(methods)) - return true; + return this.can('POST'); + } - return !!methods.find(method => typeof method === 'string' && - method.toUpperCase() === 'POST'); + /** + * This property is true when either `methods` array does not exist + * in the `self` link or the array exists and contains `DELETE` + * string. + */ + get canDelete(): boolean { + return this.can('DELETE'); } /** @@ -138,4 +141,14 @@ export abstract class HalBase { ...err.error })); } + + private can(method: string): boolean { + const methods = this._links[self]?.methods; + + if (!Array.isArray(methods)) + return true; + + return !!methods.find(m => typeof m === 'string' && + m.toUpperCase() === method); + } } diff --git a/src/lib/resource.spec.ts b/src/lib/resource.spec.ts index 52dda6a..8c9b0cd 100644 --- a/src/lib/resource.spec.ts +++ b/src/lib/resource.spec.ts @@ -603,6 +603,75 @@ describe('Resource', () => { }); }); + describe('#canDelete', () => { + it('is true when no methods array', () => { + const noMethods = new Resource({ + _client: spectator.httpClient, + _links: { + self: { href: '/api/v1/items' } + } + }); + + expect(noMethods.canDelete).toBe(true); + }); + + it('is true when methods is not an array', () => { + const notArray = new Resource({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: 'GET' + } + } + }); + + expect(notArray.canDelete).toBe(true); + }); + + it('is false when no POST in methods array', () => { + const noPost = new Resource({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET'] + } + } + }); + + expect(noPost.canDelete).toBe(false); + }); + + it('is true when DELETE is in methods array', () => { + const withPost = new Resource({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: ['GET', 'DELETE', 'PUT'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + + it('is true when DELETE matches case insensitively', () => { + const withPost = new Resource({ + _client: spectator.httpClient, + _links: { + self: { + href: '/api/v1/items', + methods: [null, 'DeLeTe'] + } + } + }); + + expect(withPost.canDelete).toBe(true); + }); + }); + describe('#delete()', () => { it('deletes resource at self link when it exists', () => { let next = false;