diff --git a/package.json b/package.json index 0c937d9..aa9047b 100644 --- a/package.json +++ b/package.json @@ -25,23 +25,24 @@ "test:ci": "jest --ci --coverage --colors", "release": "standard-version" }, - "dependencies": { - "azure-functions-ts-essentials": "1.0.0" - }, "devDependencies": { - "@types/jest": "~21.1.0", - "@types/node": "~8.0.30", - "rimraf": "~2.6.1", + "azure-functions-ts-essentials": "1.1.1", + "@types/jest": "~21.1.1", + "@types/node": "~8.0.31", + "rimraf": "~2.6.2", "webpack": "~3.6.0", "awesome-typescript-loader": "~3.2.3", "copy-webpack-plugin": "~4.1.0", - "jest": "~21.2.0", + "jest": "~21.2.1", "jest-junit-reporter": "~1.1.0", "ts-jest": "~21.0.1", "standard-version": "~4.2.0", "tslint": "~5.7.0", "backend-tslint-rules": "1.0.0", - "typescript": "~2.5.2" + "typescript": "~2.5.3" + }, + "peerDependencies": { + "azure-functions-ts-essentials": ">=1.1.0" }, "jest": { "transform": { diff --git a/src/some-function/function.json b/src/some-function/function.json index fb4e218..eac1d4b 100644 --- a/src/some-function/function.json +++ b/src/some-function/function.json @@ -2,7 +2,7 @@ "bindings": [ { "type": "httpTrigger", - "route": "some-function", + "route": "some-function/{id?}", "methods": [ "get" ], diff --git a/src/some-function/some-function.spec.ts b/src/some-function/some-function.spec.ts index 4ec724c..2614f0b 100644 --- a/src/some-function/some-function.spec.ts +++ b/src/some-function/some-function.spec.ts @@ -1,64 +1,179 @@ -import { Context, HttpRequest } from 'azure-functions-ts-essentials'; -import { run } from './some-function'; - -const testData: { name: string } = { - name: 'Azure' -}; +import { Context, HttpMethod, HttpRequest, HttpStatusCode } from 'azure-functions-ts-essentials'; +import { run, TEST_ID, TEST_REQUEST_BODY } from './some-function'; describe('@azure-seed/azure-functions-typescript', () => { - describe('generator', () => { - it('should be able to return success code w/request body', () => { + describe('some-function', () => { + it('should be able to return success code using Http GET w/`id`', () => { + const mockContext: Context = { + done: (err, response) => { + expect(err).toBeUndefined(); + + expect(response.status).toEqual(HttpStatusCode.OK); + expect(response.body).toHaveProperty('id'); + expect(response.body).toHaveProperty('object'); + expect(response.body).toHaveProperty('name'); + }, + log: () => {/**/} + }; + + const mockRequest: HttpRequest = { + method: HttpMethod.Get, + params: { + id: TEST_ID + }, + query: {}, + body: {} + }; + + run(mockContext, mockRequest); + }); + + it('should be able to return success code using Http GET', () => { const mockContext: Context = { done: (err, response) => { - expect(err).toBeUndefined(); // never call the done function with an `Error` + expect(err).toBeUndefined(); + + expect(response.status).toEqual(HttpStatusCode.OK); + expect(response.body).toHaveProperty('object'); + expect(response.body).toHaveProperty('data'); + expect(response.body).toHaveProperty('url'); + expect(response.body).toHaveProperty('hasMore'); + expect(response.body).toHaveProperty('totalCount'); + }, + log: () => {/**/} + }; + + const mockRequest: HttpRequest = { + method: HttpMethod.Get, + params: {}, + query: {}, + body: {} + }; - expect(response.status).toBe(200); // if succeeds, should return 200 - expect(response.body).toBe(`Hello ${testData.name}`); + run(mockContext, mockRequest); + }); + + it('should be able to return success code using Http POST', () => { + const mockContext: Context = { + done: (err, response) => { + expect(err).toBeUndefined(); + + expect(response.status).toEqual(HttpStatusCode.OK); + expect(response.body).toHaveProperty('id'); + expect(response.body).toHaveProperty('object'); + expect(response.body).toHaveProperty('name'); }, - log: () => {/**/} // use a jest mock here if the logs are important, in this case not + log: () => {/**/} }; const mockRequest: HttpRequest = { - body: testData, - query: {} + method: HttpMethod.Post, + params: {}, + query: {}, + body: TEST_REQUEST_BODY }; run(mockContext, mockRequest); }); - it('should be able to return success code w/request query', () => { + it('should not return success code using Http PATCH', () => { const mockContext: Context = { done: (err, response) => { expect(err).toBeUndefined(); - expect(response.status).toBe(200); - expect(response.body).toBe(`Hello ${testData.name}`); + expect(response.status).toEqual(HttpStatusCode.MethodNotAllowed); + expect(response.body).toEqual({ + error: { + type: 'not_supported', + message: 'PATCH operations are not supported.' + } + }); }, log: () => {/**/} }; const mockRequest: HttpRequest = { - body: {}, - query: testData + method: HttpMethod.Patch, + params: { + id: TEST_ID + }, + query: {}, + body: TEST_REQUEST_BODY + }; + + run(mockContext, mockRequest); + }); + + it('should be able to return success code using Http PUT', () => { + const mockContext: Context = { + done: (err, response) => { + expect(err).toBeUndefined(); + + expect(response.status).toEqual(HttpStatusCode.OK); + expect(response.body).toHaveProperty('id'); + expect(response.body).toHaveProperty('object'); + expect(response.body).toHaveProperty('name'); + }, + log: () => {/**/} + }; + + const mockRequest: HttpRequest = { + method: HttpMethod.Put, + params: { + id: TEST_ID + }, + query: {}, + body: TEST_REQUEST_BODY + }; + + run(mockContext, mockRequest); + }); + + it('should be able to return success code using Http DELETE', () => { + const mockContext: Context = { + done: (err, response) => { + expect(err).toBeUndefined(); + + expect(response.status).toEqual(HttpStatusCode.OK); + expect(response.body).toHaveProperty('deleted'); + expect(response.body).toHaveProperty('id'); + }, + log: () => {/**/} + }; + + const mockRequest: HttpRequest = { + method: HttpMethod.Delete, + params: { + id: TEST_ID + }, + query: {}, + body: {} }; run(mockContext, mockRequest); }); - it('should not return success code w/o any input', () => { + it('should not return success code using any other Http method', () => { const mockContext: Context = { done: (err, response) => { expect(err).toBeUndefined(); - expect(response.status).toBe(400); - expect(response.body).toBe('Please pass a name on the query string or in the request body'); + expect(response.status).toEqual(HttpStatusCode.MethodNotAllowed); + expect(response.body).toEqual({ + error: { + type: 'not_supported', + message: 'Method XYZ not supported.' + } + }); }, log: () => {/**/} }; const mockRequest: HttpRequest = { - body: {}, - query: {} + method: 'XYZ' as HttpMethod, + params: {}, + query: {}, + body: {} }; run(mockContext, mockRequest); diff --git a/src/some-function/some-function.ts b/src/some-function/some-function.ts index d52aa45..eeec5d5 100644 --- a/src/some-function/some-function.ts +++ b/src/some-function/some-function.ts @@ -1,20 +1,127 @@ -import { Context, HttpRequest, HttpResponse } from 'azure-functions-ts-essentials'; +import { Context, HttpMethod, HttpRequest, HttpResponse, HttpStatusCode } from 'azure-functions-ts-essentials'; +const OBJECT_NAME = 'someObject'; + +const getOne = (id: any) => { + return { + status: HttpStatusCode.OK, + body: { + id, + object: OBJECT_NAME, + ...TEST_REQUEST_BODY + } + }; +}; + +const getMany = (req: HttpRequest) => { + return { + status: HttpStatusCode.OK, + body: { + object: 'list', + data: [ + { + id: TEST_ID, + object: OBJECT_NAME, + ...TEST_REQUEST_BODY + } + ], + url: '/some-function', + hasMore: false, + totalCount: 1 + } + }; +}; + +const insertOne = (req: HttpRequest) => { + return { + status: HttpStatusCode.OK, + body: { + id: TEST_ID, + object: OBJECT_NAME, + ...req.body + } + }; +}; + +const patchOne = (req: HttpRequest, id: any) => { + return { + status: HttpStatusCode.MethodNotAllowed, + body: { + error: { + type: 'not_supported', + message: 'PATCH operations are not supported.' + } + } + }; +}; + +const updateOne = (req: HttpRequest, id: any) => { + return { + status: HttpStatusCode.OK, + body: { + id, + object: OBJECT_NAME, + ...req.body + } + }; +}; + +const deleteOne = (id: any) => { + return { + status: HttpStatusCode.OK, + body: { + deleted: true, + id + } + }; +}; + +export const TEST_ID = '57ade20771e59f422cc652d9'; +export const TEST_REQUEST_BODY: { name: string } = { + name: 'Azure' +}; + +/** + * Routes the request to the default controller using the relevant method. + * + * @param {Context} context + * @param {HttpRequest} req + * @returns {any} + */ export function run(context: Context, req: HttpRequest): any { - context.log('TypeScript HTTP trigger function processed a request.'); - - let res: HttpResponse | null = null; - - if (req.query.name || (req.body && req.body.name)) - res = { - status: 200, - body: `Hello ${req.query.name || req.body.name}` - }; - else - res = { - status: 400, - body: 'Please pass a name on the query string or in the request body' - }; + let res: HttpResponse; + const id = req.params.id; + + switch (req.method) { + case HttpMethod.Get: + res = id + ? getOne(id) + : getMany(req); + break; + case HttpMethod.Post: + res = insertOne(req); + break; + case HttpMethod.Patch: + res = patchOne(req, id); + break; + case HttpMethod.Put: + res = updateOne(req, id); + break; + case HttpMethod.Delete: + res = deleteOne(id); + break; + + default: + res = { + status: HttpStatusCode.MethodNotAllowed, + body: { + error: { + type: 'not_supported', + message: `Method ${req.method} not supported.` + } + } + }; + } context.done(undefined, res); } diff --git a/yarn.lock b/yarn.lock index 8468971..14c6bfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,11 @@ # yarn lockfile v1 -"@types/jest@~21.1.0": +"@types/jest@~21.1.1": version "21.1.1" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.1.tgz#70008ecce439774a45c7d0b543d879ec58a1b332" -"@types/node@~8.0.30": +"@types/node@~8.0.31": version "8.0.31" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.31.tgz#d9af61093cf4bfc9f066ca34de0175012cfb0ce9" @@ -248,9 +248,9 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -azure-functions-ts-essentials@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/azure-functions-ts-essentials/-/azure-functions-ts-essentials-1.0.0.tgz#9befe22db1e8e9c9b6c926162aeec8dbc5dff68a" +azure-functions-ts-essentials@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/azure-functions-ts-essentials/-/azure-functions-ts-essentials-1.1.1.tgz#c7bc40ac3572461775582a19bda6f64bb77e6a5f" babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" @@ -942,7 +942,7 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -copy-webpack-plugin@4.1.0: +copy-webpack-plugin@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.0.tgz#292a040318fe8ae3b1d7996ef05dfb483eb0b647" dependencies: @@ -2484,7 +2484,7 @@ jest-validate@^21.2.1: leven "^2.1.0" pretty-format "^21.2.1" -jest@~21.2.0: +jest@~21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1" dependencies: @@ -3534,7 +3534,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@~2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@~2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -4073,7 +4073,7 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@~2.5.2: +typescript@~2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d"