diff --git a/packages/core/src/backend/servers/http/core.ts b/packages/core/src/backend/servers/http/core.ts index 3c2f696..25194e0 100644 --- a/packages/core/src/backend/servers/http/core.ts +++ b/packages/core/src/backend/servers/http/core.ts @@ -14,8 +14,10 @@ import { BaseResourceType, CanPatchSpec, DELTA_SCHEMA, + getAcceptPatchString, + getAcceptPostString, LanguageDefaultErrorStatusMessageKey, - PATCH_CONTENT_MAP_TYPE, + PATCH_CONTENT_MAP_TYPE, PATCH_CONTENT_TYPES, PatchContentType, Resource, } from '../../../common'; @@ -235,22 +237,24 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr : charsetRaw.trim() ) ?? (isTextMediaType(mediaType) ? 'utf-8' : 'binary'); - if (effectiveMethod === 'PATCH') { - const validPatchTypes = Object.entries(req.resource.state.canPatch) - .filter(([, allowed]) => allowed) - .map(([patchType]) => patchType); - - const validPatchContentTypes = Object.entries(PATCH_CONTENT_MAP_TYPE) - .filter(([ patchType]) => validPatchTypes.includes(patchType)) - .map(([contentType ]) => contentType); + if (effectiveMethod === 'POST' && PATCH_CONTENT_TYPES.includes(mediaType as PatchContentType)) { + throw new ErrorPlainResponse('invalidResource', { + statusCode: constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + res: theRes, + headers: { + 'Accept-Post': getAcceptPostString(req.backend.app.mediaTypes), + }, + }); + } + if (effectiveMethod === 'PATCH') { const isPatchEnabled = req.resource.state.canPatch[PATCH_CONTENT_MAP_TYPE[mediaType as PatchContentType]]; if (!isPatchEnabled) { throw new ErrorPlainResponse('invalidResourcePatchType', { statusCode: constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, res: theRes, headers: { - 'Accept-Patch': validPatchContentTypes.join(', '), + 'Accept-Patch': getAcceptPatchString(req.resource.state.canPatch), }, }); } @@ -549,7 +553,9 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr 'Content-Language': language.name, }; if (resourceReq.method === 'POST') { - headers['Accept-Post'] = Array.from(resourceReq.backend.app.mediaTypes.keys()).join(','); + headers['Accept-Post'] = Array.from(resourceReq.backend.app.mediaTypes.keys()) + .filter((t) => !Object.keys(PATCH_CONTENT_MAP_TYPE).includes(t)) + .join(','); } else if (resourceReq.method === 'PATCH') { headers['Accept-Patch'] = Array.from(Object.entries(PATCH_CONTENT_MAP_TYPE)) .filter(([, value]) => Object.keys(resourceReq.resource.state.canPatch).includes(value)) diff --git a/packages/core/src/backend/servers/http/handlers/default.ts b/packages/core/src/backend/servers/http/handlers/default.ts index 1d2737c..5692c90 100644 --- a/packages/core/src/backend/servers/http/handlers/default.ts +++ b/packages/core/src/backend/servers/http/handlers/default.ts @@ -2,7 +2,7 @@ import {constants} from 'http2'; import {AllowedMiddlewareSpecification, Middleware} from '../../../common'; import {LinkMap} from '../utils'; import {PlainResponse, ErrorPlainResponse} from '../response'; -import {PATCH_CONTENT_MAP_TYPE} from '../../../../common'; +import {getAcceptPatchString, getAcceptPostString} from '../../../../common'; export const handleGetRoot: Middleware = (req, res) => { const { backend, basePath } = req; @@ -47,17 +47,17 @@ export const handleOptions = (middlewares: AllowedMiddlewareSpecification[]): Mi const headers: Record = { 'Allow': allowedMethods.join(', '), }; + + if (allowedMethods.includes('POST')) { + headers['Accept-Post'] = getAcceptPostString(req.backend.app.mediaTypes); + } + if (allowedMethods.includes('PATCH')) { const validPatchTypes = Object.entries(req.resource.state.canPatch) - .filter(([, allowed]) => allowed) - .map(([patchType]) => patchType); - - const validPatchContentTypes = Object.entries(PATCH_CONTENT_MAP_TYPE) - .filter(([, patchType]) => validPatchTypes.includes(patchType)) - .map(([contentType ]) => contentType); + .filter(([, allowed]) => allowed); - if (validPatchContentTypes.length > 0) { - headers['Accept-Patch'] = validPatchContentTypes.join(', '); + if (validPatchTypes.length > 0) { + headers['Accept-Patch'] = getAcceptPatchString(req.resource.state.canPatch); } } return new PlainResponse({ diff --git a/packages/core/src/common/media-type.ts b/packages/core/src/common/media-type.ts index 7d66754..3d0c70e 100644 --- a/packages/core/src/common/media-type.ts +++ b/packages/core/src/common/media-type.ts @@ -16,3 +16,7 @@ export const PATCH_CONTENT_TYPES = [ ] as const; export type PatchContentType = typeof PATCH_CONTENT_TYPES[number]; + +export const getAcceptPostString = (mediaTypes: Map) => Array.from(mediaTypes.keys()) + .filter((t) => !PATCH_CONTENT_TYPES.includes(t as PatchContentType)) + .join(','); diff --git a/packages/core/src/common/resource.ts b/packages/core/src/common/resource.ts index 10a3a37..ad08670 100644 --- a/packages/core/src/common/resource.ts +++ b/packages/core/src/common/resource.ts @@ -170,3 +170,14 @@ export const resource = = v.Output; + +export const getAcceptPatchString = (canPatch: CanPatchObject) => { + const validPatchTypes = Object.entries(canPatch) + .filter(([, allowed]) => allowed) + .map(([patchType]) => patchType); + + return Object.entries(PATCH_CONTENT_MAP_TYPE) + .filter(([, patchType]) => validPatchTypes.includes(patchType)) + .map(([contentType ]) => contentType) + .join(','); +} diff --git a/packages/core/test/handlers/http/default.test.ts b/packages/core/test/handlers/http/default.test.ts index 8e0196d..ab17e41 100644 --- a/packages/core/test/handlers/http/default.test.ts +++ b/packages/core/test/handlers/http/default.test.ts @@ -28,6 +28,8 @@ const ACCEPT_CHARSET = 'utf-8'; const CONTENT_TYPE_CHARSET = 'utf-8'; const CONTENT_TYPE = ACCEPT; +const prepareStatusMessage = (s: string) => s.replace(/\$RESOURCE/g, 'Piano'); + describe('happy path', () => { let Piano: Resource; let app: Application; @@ -125,7 +127,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceCollectionFetched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceCollectionFetched)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); if (typeof resData === 'undefined') { @@ -143,7 +145,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceCollectionFetched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceCollectionFetched)); }); it('returns options', async () => { @@ -153,7 +155,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('GET'); expect(allowedMethods).toContain('HEAD'); @@ -187,7 +189,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceFetched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceFetched)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); if (typeof resData === 'undefined') { expect.fail('Response body must be defined.'); @@ -204,7 +206,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceFetched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceFetched)); }); it('returns options', async () => { @@ -214,7 +216,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('GET'); expect(allowedMethods).toContain('HEAD'); @@ -259,7 +261,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_CREATED); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceCreated); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceCreated)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); expect(res.headers).toHaveProperty('location', `${BASE_PATH}/pianos/2`); @@ -281,7 +283,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('POST'); }); @@ -327,7 +329,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('PATCH'); const acceptPatch = res.headers['accept-patch']?.split(',').map((s) => s.trim()) ?? []; @@ -351,7 +353,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourcePatched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourcePatched)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); if (typeof resData === 'undefined') { @@ -388,7 +390,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourcePatched); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourcePatched)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); if (typeof resData === 'undefined') { @@ -438,7 +440,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_OK); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceReplaced); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceReplaced)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); if (typeof resData === 'undefined') { @@ -470,7 +472,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_CREATED); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceCreated); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceCreated)); expect(res.headers).toHaveProperty('content-type', expect.stringContaining(ACCEPT)); expect(res.headers).toHaveProperty('location', `${BASE_PATH}/pianos/${newId}`); @@ -492,7 +494,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('PUT'); }); @@ -531,7 +533,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.resourceDeleted); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.resourceDeleted)); expect(res.headers).not.toHaveProperty('content-type'); expect(resData).toBeUndefined(); }); @@ -543,7 +545,7 @@ describe('happy path', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NO_CONTENT); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.provideOptions); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.provideOptions)); const allowedMethods = res.headers.allow?.split(',').map((s) => s.trim()) ?? []; expect(allowedMethods).toContain('DELETE'); }); diff --git a/packages/core/test/handlers/http/error-handling.test.ts b/packages/core/test/handlers/http/error-handling.test.ts index 094ea60..6a404e4 100644 --- a/packages/core/test/handlers/http/error-handling.test.ts +++ b/packages/core/test/handlers/http/error-handling.test.ts @@ -28,6 +28,8 @@ const ACCEPT_CHARSET = 'utf-8'; const CONTENT_TYPE_CHARSET = 'utf-8'; const CONTENT_TYPE = ACCEPT; +const prepareStatusMessage = (s: string) => s.replace(/\$RESOURCE/g, 'Piano'); + describe('error handling', () => { let Piano: Resource; let app: Application; @@ -123,7 +125,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResourceCollection); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResourceCollection)); }); it('throws on HEAD method', async () => { @@ -137,7 +139,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResourceCollection); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResourceCollection)); }); }); @@ -161,7 +163,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResource)); }); it('throws on HEAD method', async () => { @@ -175,7 +177,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResource)); }); it('throws on item not found', async () => { @@ -234,7 +236,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToAssignIdFromResourceDataSource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToAssignIdFromResourceDataSource)); }); it('throws on error creating resource', async () => { @@ -253,7 +255,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToCreateResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToCreateResource)); }); }); @@ -285,7 +287,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResource)); Piano.canPatch(false); }); @@ -305,7 +307,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.patchNonExistingResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.patchNonExistingResource)); Piano.canPatch(false); }); @@ -333,7 +335,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.invalidResourcePatchType); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.invalidResourcePatchType)); }); }); @@ -357,7 +359,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.invalidResourcePatchType); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.invalidResourcePatchType)); }); it('throws on operating with a delta to an attribute outside the schema', async () => { @@ -377,7 +379,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_UNPROCESSABLE_ENTITY); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.invalidResourcePatch); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.invalidResourcePatch)); }); it('throws on operating a delta with mismatched value type', async () => { @@ -397,7 +399,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_UNPROCESSABLE_ENTITY); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.invalidResourcePatch); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.invalidResourcePatch)); }); it('throws on performing an invalid delta', async () => { @@ -417,7 +419,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_UNPROCESSABLE_ENTITY); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.invalidResourcePatch); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.invalidResourcePatch)); }); }); }); @@ -459,7 +461,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToEmplaceResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToEmplaceResource)); }); }); @@ -490,7 +492,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToFetchResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToFetchResource)); }); it('throws on item not found', async () => { @@ -504,7 +506,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.deleteNonExistingResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.deleteNonExistingResource)); }); it('throws on unable to delete item', async () => { @@ -522,7 +524,7 @@ describe('error handling', () => { }); expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); - expect(res).toHaveProperty('statusMessage', TEST_LANGUAGE.statusMessages.unableToDeleteResource); + expect(res).toHaveProperty('statusMessage', prepareStatusMessage(TEST_LANGUAGE.statusMessages.unableToDeleteResource)); }); }); }); diff --git a/packages/examples/cms-web-api/bruno/Check Allowed Post Operations.bru b/packages/examples/cms-web-api/bruno/Check Allowed Post Operations.bru new file mode 100644 index 0000000..17d52b0 --- /dev/null +++ b/packages/examples/cms-web-api/bruno/Check Allowed Post Operations.bru @@ -0,0 +1,11 @@ +meta { + name: Check Allowed Post Operations + type: http + seq: 10 +} + +options { + url: http://localhost:6969/api/posts/9ba60691-0cd3-4e8a-9f44-e92b19fcacbc + body: none + auth: none +} diff --git a/packages/examples/cms-web-api/bruno/Check Allowed Posts Operations.bru b/packages/examples/cms-web-api/bruno/Check Allowed Posts Operations.bru new file mode 100644 index 0000000..72f933d --- /dev/null +++ b/packages/examples/cms-web-api/bruno/Check Allowed Posts Operations.bru @@ -0,0 +1,11 @@ +meta { + name: Check Allowed Posts Operations + type: http + seq: 11 +} + +options { + url: http://localhost:6969/api/posts + body: none + auth: none +}