diff --git a/packages/example-getting-started/src/controllers/todo.controller.ts b/packages/example-getting-started/src/controllers/todo.controller.ts index 6d91ace47c2a..8f88213654ed 100644 --- a/packages/example-getting-started/src/controllers/todo.controller.ts +++ b/packages/example-getting-started/src/controllers/todo.controller.ts @@ -1,63 +1,56 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Filter, Where} from '@loopback/repository'; import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; -import {inject} from '@loopback/context'; -import {Todo} from '../models'; -import {TodoRepository} from '../repositories'; +import {HttpErrors} from '@loopback/rest'; +import {TodoSchema, Todo} from '../models'; +import {repository} from '@loopback/repository'; +import {TodoRepository} from '../repositories/index'; export class TodoController { + // TODO(bajtos) Fix documentation (and argument names?) of @repository() + // to allow the usage below. + // See https://github.com/strongloop/loopback-next/issues/744 constructor( - @inject('repositories.TodoRepository') - public todoRepository: TodoRepository, + @repository(TodoRepository.name) protected todoRepo: TodoRepository, ) {} - @post('/todo') - async create(@param.body('obj') obj: Todo): Promise { - return await this.todoRepository.create(obj); + @param.body('todo', TodoSchema) + async createTodo(todo: Todo) { + // TODO(bajtos) This should be handled by the framework + // See https://github.com/strongloop/loopback-next/issues/118 + if (!todo.title) { + return Promise.reject(new HttpErrors.BadRequest('title is required')); + } + return await this.todoRepo.create(todo); } - @get('/todo/count') - async count(@param.query.string('where') where: Where): Promise { - return await this.todoRepository.count(where); + @get('/todo/{id}') + @param.path.number('id') + @param.query.boolean('items') + async findTodoById(id: number, items?: boolean): Promise { + return await this.todoRepo.findById(id); } @get('/todo') - async find(@param.query.string('filter') filter: Filter): Promise { - return await this.todoRepository.find(filter); - } - - @patch('/todo') - async updateAll( - @param.query.string('where') where: Where, - @param.body('obj') obj: Todo, - ): Promise { - return await this.todoRepository.updateAll(where, obj); - } - - @del('/todo') - async deleteAll(@param.query.string('where') where: Where): Promise { - return await this.todoRepository.deleteAll(where); + async findTodos(): Promise { + return await this.todoRepo.find(); } - @get('/todo/{id}') - async findById(@param.path.number('id') id: number): Promise { - return await this.todoRepository.findById(id); + @put('/todo/{id}') + @param.path.number('id') + @param.body('todo', TodoSchema) + async replaceTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.replaceById(id, todo); } @patch('/todo/{id}') - async updateById( - @param.path.number('id') id: number, - @param.body('obj') obj: Todo, - ): Promise { - return await this.todoRepository.updateById(id, obj); + @param.path.number('id') + @param.body('todo', TodoSchema) + async updateTodo(id: number, todo: Todo): Promise { + return await this.todoRepo.updateById(id, todo); } @del('/todo/{id}') - async deleteById(@param.path.number('id') id: number): Promise { - return await this.todoRepository.deleteById(id); + @param.path.number('id') + async deleteTodo(id: number): Promise { + return await this.todoRepo.deleteById(id); } } diff --git a/packages/example-getting-started/src/models/todo.model.ts b/packages/example-getting-started/src/models/todo.model.ts index ba529a7444b6..43a092bd481a 100644 --- a/packages/example-getting-started/src/models/todo.model.ts +++ b/packages/example-getting-started/src/models/todo.model.ts @@ -1,26 +1,29 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {Entity, property, model} from '@loopback/repository'; import {SchemaObject} from '@loopback/openapi-spec'; @model() export class Todo extends Entity { @property({ + type: 'number', id: true, }) id?: number; @property({ + type: 'string', required: true, }) title: string; - @property() desc?: string; + @property({ + type: 'string', + }) + desc?: string; - @property() isComplete: boolean; + @property({ + type: 'boolean', + }) + isComplete: boolean; getId() { return this.id; diff --git a/packages/example-getting-started/test/acceptance/application.test.ts b/packages/example-getting-started/test/acceptance/application.test.ts index e217795dc761..30fb5ba4418f 100644 --- a/packages/example-getting-started/test/acceptance/application.test.ts +++ b/packages/example-getting-started/test/acceptance/application.test.ts @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {createClientForHandler, expect, supertest} from '@loopback/testlab'; import {RestServer} from '@loopback/rest'; import {TodoApplication} from '../../src/application'; @@ -48,6 +43,21 @@ describe('Application', () => { .expect(200, todo); }); + it('replaces the todo by ID', async () => { + const todo = await givenTodoInstance(); + const updatedTodo = givenTodo({ + title: 'DO SOMETHING AWESOME', + desc: 'It has to be something ridiculous', + isComplete: true, + }); + await client + .put(`/todo/${todo.id}`) + .send(updatedTodo) + .expect(200); + const result = await todoRepo.findById(todo.id); + expect(result).to.containEql(updatedTodo); + }); + it('updates the todo by ID ', async () => { const todo = await givenTodoInstance(); const updatedTodo = givenTodo({ diff --git a/packages/example-getting-started/test/helpers.ts b/packages/example-getting-started/test/helpers.ts index ca674cb8ba63..a14e478f01f8 100644 --- a/packages/example-getting-started/test/helpers.ts +++ b/packages/example-getting-started/test/helpers.ts @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {Todo} from '../src/models/index'; /* @@ -13,7 +8,7 @@ import {Todo} from '../src/models/index'; way to reduce duplication. Other tips: - + - Using the super awesome Partial type in conjunction with Object.assign means you can: * customize the object you get back based only on what's important diff --git a/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts b/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts index 06be5a5b8434..860c2ba8c12b 100644 --- a/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts +++ b/packages/example-getting-started/test/unit/controllers/todo.controller.test.ts @@ -1,8 +1,3 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - import {expect} from '@loopback/testlab'; import {TodoController} from '../../../src/controllers'; import {TodoRepository} from '../../../src/repositories'; @@ -45,20 +40,36 @@ describe('TodoController', () => { let aChangedTodo: Todo; let aTodoList: Todo[]; + const noError = 'No error was thrown!'; + beforeEach(resetRepositories); describe('createTodo', () => { it('creates a Todo', async () => { create.resolves(aTodoWithId); - const result = await controller.create(aTodo); + const result = await controller.createTodo(aTodo); expect(result).to.eql(aTodoWithId); sinon.assert.calledWith(create, aTodo); }); + + it('throws if the payload is missing a title', async () => { + const todo = givenTodo(); + delete todo.title; + try { + await controller.createTodo(todo); + } catch (err) { + expect(err).to.match(/title is required/); + sinon.assert.notCalled(create); + return; + } + // Repository stub should not have been called! + throw new Error(noError); + }); }); describe('findTodoById', () => { it('returns a todo if it exists', async () => { findById.resolves(aTodoWithId); - expect(await controller.findById(aTodoWithId.id as number)).to.eql( + expect(await controller.findTodoById(aTodoWithId.id as number)).to.eql( aTodoWithId, ); sinon.assert.calledWith(findById, aTodoWithId.id); @@ -68,23 +79,33 @@ describe('TodoController', () => { describe('findTodos', () => { it('returns multiple todos if they exist', async () => { find.resolves(aTodoList); - expect(await controller.find({})).to.eql(aTodoList); + expect(await controller.findTodos()).to.eql(aTodoList); sinon.assert.called(find); }); it('returns empty list if no todos exist', async () => { const expected: Todo[] = []; find.resolves(expected); - expect(await controller.find({})).to.eql(expected); + expect(await controller.findTodos()).to.eql(expected); sinon.assert.called(find); }); }); + describe('replaceTodo', () => { + it('successfully replaces existing items', async () => { + replaceById.resolves(true); + expect( + await controller.replaceTodo(aTodoWithId.id as number, aChangedTodo), + ).to.eql(true); + sinon.assert.calledWith(replaceById, aTodoWithId.id, aChangedTodo); + }); + }); + describe('updateTodo', () => { it('successfully updates existing items', async () => { updateById.resolves(true); expect( - await controller.updateById(aTodoWithId.id as number, aChangedTodo), + await controller.updateTodo(aTodoWithId.id as number, aChangedTodo), ).to.eql(true); sinon.assert.calledWith(updateById, aTodoWithId.id, aChangedTodo); }); @@ -93,7 +114,7 @@ describe('TodoController', () => { describe('deleteTodo', () => { it('successfully deletes existing items', async () => { deleteById.resolves(true); - expect(await controller.deleteById(aTodoWithId.id as number)).to.eql( + expect(await controller.deleteTodo(aTodoWithId.id as number)).to.eql( true, ); sinon.assert.calledWith(deleteById, aTodoWithId.id);