diff --git a/.gitignore b/.gitignore index e43b0f9..abbc21a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Debug log from npm +npm-debug.log + .DS_Store +db/ diff --git a/README.md b/README.md index f75cb52..3f53b8e 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,5 @@ The API you build should have the following capabilities. The schema of your dat - We will use [Mocha](https://mochajs.org/) for tests. - There isn't a coverage requirement for this project, beyond demonstrating that every endpoint is covered by some manner of tests. +#### Run once prior to running tests +`DB=test npm run db:schema` diff --git a/app.js b/app.js new file mode 100644 index 0000000..1ca3876 --- /dev/null +++ b/app.js @@ -0,0 +1,52 @@ +var express = require('express'); +var path = require('path'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var app = express(); + +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +// routes +app.use('/customers', require('./routes/customers')); +app.use('/movies', require('./routes/movies')); +app.use('/rentals', require('./routes/rentals')); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..cc43085 --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('C3Projects--VideoStoreAPI:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/controllers/customers.js b/controllers/customers.js new file mode 100644 index 0000000..9881c09 --- /dev/null +++ b/controllers/customers.js @@ -0,0 +1,44 @@ +"use strict"; + +var Customer = require('../models/customer'); + +var Controller = { + index: function(req, res, next) { + if (req.query.status == 'overdue') { + new Customer().overdue(Controller.sendJSON.bind(res)); + } else if (req.query.sort) { + new Customer().sortBy(req.query.sort, req.query.n, req.query.p, Controller.sendJSON.bind(res)); + } else { + new Customer().all(Controller.sendJSON.bind(res)); + } + }, + + rentals: function(req, res, next) { + new Customer().rentals(req.params.id, Controller.formatRentalsSendJON.bind(res)); + }, + + formatRentalsSendJON: function(err, res) { + var results = { current: [], past: [] }; + + for (var i = 0; i < res.length; i++) { + if (res[i].return_date) { + results.past.push(res[i]); + } else { + results.current.push(res[i]); + } + } + + Controller.sendJSON.call(this, err, results); + }, + + sendJSON: function(err, res) { + if (err) { + var status = err.status == 400 ? 400 : 500; + this.status(status).json(err.message); + } else { + this.status(200).json(res); + } + } +} + +module.exports = Controller; diff --git a/controllers/movies.js b/controllers/movies.js new file mode 100644 index 0000000..0318477 --- /dev/null +++ b/controllers/movies.js @@ -0,0 +1,49 @@ +"use strict"; + +var Movie = require('../models/movie'); + +var Controller = { + index: function(req, res, next) { + if (req.query.sort) { + new Movie().sortBy(req.query.sort, req.query.n, req.query.p, Controller.sendJSON.bind(res)); + } else { + new Movie().all(Controller.sendJSON.bind(res)); + } + }, + + show: function(req, res, next) { + new Movie().findBy('title', req.params.title, Controller.formatThenSendJSON.bind(res)); + }, + + formatThenSendJSON: function(err, results) { + new Movie().numAvail(results[0].title, function(err, otherResults) { + results = results[0]; + results.num_available = otherResults[0].num_available; + + Controller.sendJSON.apply(this, [err, results]); + + }.bind(this)); + + }, + + customers: function(req, res, next) { + if (req.query.status == 'current') { + new Movie().customersCurrent(req.params.title, Controller.sendJSON.bind(res)); + } else if (req.query.status == 'past') { + new Movie().customersPast(req.params.title, req.query.sort, Controller.sendJSON.bind(res)); + } else { + res.status(400).json("Bad request"); + } + }, + + sendJSON: function(err, res) { + if (err) { + var status = err.status == 400 ? 400 : 500; + this.status(status).json(err.message); + } else { + this.status(200).json(res); + } + } +} + +module.exports = Controller; diff --git a/controllers/rentals.js b/controllers/rentals.js new file mode 100644 index 0000000..0ddd055 --- /dev/null +++ b/controllers/rentals.js @@ -0,0 +1,25 @@ +"use strict"; + +var Rental = require('../models/rental'); + +var Controller = { + create: function(req, res, next) { + var rental = new Rental() + rental.checkOut(req.body, Controller.sendJSON.bind(res)); + }, + + update: function(req, res, next) { + new Rental().checkIn(req.body.movie_title, req.body.return_date, req.params.customer_id, Controller.sendJSON.bind(res)) + }, + + sendJSON: function(err, res) { + if (err) { + var status = err.status == 400 ? 400 : 500; + this.status(status).json(err.message); + } else { + this.status(200).json(res); + } + } +} + +module.exports = Controller; diff --git a/documentation.txt b/documentation.txt new file mode 100644 index 0000000..6f0a166 --- /dev/null +++ b/documentation.txt @@ -0,0 +1,69 @@ +### Customers +GET '/customers' + // => [{}, {}] <-- includes customer attributes + - See a list of customers with overdue movies + // ?&status=overdue + - Retrive a subset of customers + - Given a sort column, return _n_ customer records, offset by _p_ records (this will be used to create "pages" of customers) + // ?&n=12&p=2&sort=_____ + // sort options: name, registered_at, postal_code + - Sort columns are + - `name` + - `registered_at` + - `postal_code` +GET 'customers/:id/rentals' + - Given a customers `id`... + - List the movies they _currently_ have checked out + + include rental id + - List the movies a customer has checked out in the past + + include rental id + - ordered by check out date + - includes return date + +### Movies +GET '/movies' + // => [{}, {}] <-- includes movie attributes + // OPTIONAL + // ?n=12&p=2&sort=_____ + // sort options: title, release_date + - Retrieve a list of all movies + - Retrieve a subset of movies + - Given a sort column, return _n_ movie records, offset by _p_ records (this will be used to create "pages" of movies) + - Sort columns are + - `title` + - `release_date` +GET 'movies/:title/customers' + // => {} <-- title is REQUIRED TO BE unique + // ?status=____&sort=_____ + // status options: current, past + // status required for this endpoint + // sort options: id, name, checkout_date + // sort required for status past + - Given a movies `title`... + + include rental ids + - Get a list of customers that have _currently_ checked out a copy of the film + - See a list of customers that have _currently_ checked out any of the movie's inventory + + - Get a list of customers that have checked out a copy _in the past_ + - ordered by customer `id` + - ordered by customer `name` + - ordered by check out date + +GET '/movies/:title' + // => {} <-- title is REQUIRED TO BE unique + - Look a movie up by title to see + - its synopsis + - release date + - and inventory total + - Know if a movie has any inventory available to rent (num avail, num checked out, which/whatever) + +### Rental +- Given a customer's `id` and a movie's `title` ... +POST '/rentals' + // in the body, include customer/movie details + - "check out" one of the movies inventory to the customer + - Establish a return date + - Charge the customers account (cost up to you) +PUT '/rentals/:customer_id' + - "check in" one of customers rentals + - return the movie to its inventory diff --git a/models/customer.js b/models/customer.js new file mode 100644 index 0000000..569d9f3 --- /dev/null +++ b/models/customer.js @@ -0,0 +1,54 @@ +"use strict"; + +function Customer() { + this.tableName = 'customers'; + this.columnNames = [ + 'id', // INTEGER PRIMARY KEY + 'name', // TEXT NOT NULL + 'registered_at', // TEXT + 'address', // TEXT + 'city', // TEXT + 'state', // TEXT + 'postal_code', // TEXT + 'phone TEXT', // TEXT + 'account_balance' // INTEGER NOT NULL DEFAULT 0 + ]; +} + +Customer.prototype = require('./database').prototype; + +Customer.prototype.rentals = function rentals(customerID, callback) { + var db = this.openDB(); + var statement = 'SELECT "rentals".* FROM "rentals" WHERE "rentals"."customer_id" = ? ORDER BY "rentals"."checkout_date" ASC'; + + db.all(statement, customerID, function(err, rows) { + if (err) { console.log('!!!!ERROR!!!! In Customer#rentals.'); } // FIXME: how is error tracking best handled? + + callback(err, rows); + db.close(); + }); +} + +Customer.prototype.overdue = function overdue(callback) { + var db = this.openDB(); + var statement = "SELECT customers.id AS customer_id, customers.name, rentals.movie_title, rentals.checkout_date, rentals.id AS rental_id FROM customers INNER JOIN rentals ON customers.id = rentals.customer_id WHERE rentals.return_date = '' AND date('now') > DATETIME(rentals.checkout_date, '+7 days');"; + + db.all(statement, function(err, rows) { + callback(err, rows); + db.close(); + }); +} + +// Customer.prototype.movies = function movies(customerID, callback) { +// var db = this.openDB(); +// var statement = 'SELECT "movies".* FROM "movies" INNER JOIN "rentals" ON "movies"."title" = "rentals"."movie_title" WHERE "rentals"."customer_id" = ?'; + +// db.all(statement, customerID, function(err, rows) { +// if (err) { console.log('!!!!ERROR!!!! In Customer#movies.'); } // FIXME: how is error tracking best handled? + +// callback(err, rows); +// db.close(); +// }); +// } + +module.exports = Customer; diff --git a/models/database.js b/models/database.js new file mode 100644 index 0000000..b645648 --- /dev/null +++ b/models/database.js @@ -0,0 +1,113 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db_path = 'db/' + db_env + '.db'; + +function Database() {}; + +Database.prototype.openDB = function openDB() { + return new sqlite3.Database(this.dbPath()); +} + +Database.prototype.dbPath = function dbPath() { + return db_path; +} + +Database.prototype.create = function create(data, callback) { + var db = this.openDB(); + var keys = Object.keys(data); + var questionMarks = []; + var values = []; + + for (var i = 0; i < keys.length; i++) { + values.push(data[keys[i]]); + questionMarks.push("?"); + } + + var statement = "INSERT INTO " + this.tableName + " (" + keys.join(", ") + ") VALUES (" + questionMarks.join(", ") + ");"; + + db.run(statement, values, function(err) { + if (err) { + callback(err, { insertedID: null, changed: null }); + } else { + callback(err, { insertedID: this.lastID, changed: this.changes }); + } + + db.close(); + }); +} + +Database.prototype.all = function all(callback) { + var db = this.openDB(); + var statement = 'SELECT * FROM ' + this.tableName + ';'; + + db.all(statement, function(err, rows) { + if (err) { console.log('!!!!ERROR!!!! In Database#all.'); } // FIXME: how is error tracking best handled? + + callback(err, rows); + db.close(); + }); +} + +// OPTIMIZE / TODO: this can only search by one parameter at a time, and only with an `=` relationship. +Database.prototype.findBy = function findBy(parameter, value, callback) { + // check that the parameter is a valid parameter (e.g. make sure it's not sql injection) + if (!this._validParam(parameter)) { + callback(new Error('Error: syntax error. Unrecognized parameter.')); + return; + } + + var db = this.openDB(); + var statement = 'SELECT * FROM ' + this.tableName + ' WHERE ' + parameter + ' LIKE ?;'; + + db.all(statement, value, function(err, rows) { + if (err) { console.log('!!!!ERROR!!!! In Database#findBy'); } // FIXME: how is error tracking best handled? + + callback(err, rows); + db.close(); + }); +} + +Database.prototype.sortBy = function sortBy(parameter, n, p, callback) { + if (!this._validParam(parameter)) { + var error = new Error('Bad request'); + error.status = 400; + callback(error); + return; + } + + var values = []; + n = parseInt(n); + p = parseInt(p); + + if (!n) { // if n is NaN/null/undefined + var statement = 'SELECT * FROM ' + this.tableName + ' ORDER BY ' + parameter + ';'; + } else if (p > 1) { + var offset = n * (p - 1); + values.push(n, offset); + var statement = 'SELECT * FROM ' + this.tableName + ' ORDER BY ' + parameter + ' LIMIT ? OFFSET ?;'; + } else { + values.push(n); + var statement = 'SELECT * FROM ' + this.tableName + ' ORDER BY ' + parameter + ' LIMIT ?;'; + } + + var db = this.openDB(); + db.all(statement, values, function(err, rows) { + callback(err, rows); + db.close(); + }); +} + +Database.prototype._validParam = function _validParam(parameter) { + parameter = parameter.toLowerCase(); + + for (var i = 0; i < this.columnNames.length; i++) { + if (this.columnNames[i] == parameter) { + return true; + } + } + return false; +} + +module.exports = Database; diff --git a/models/movie.js b/models/movie.js new file mode 100644 index 0000000..cf0e642 --- /dev/null +++ b/models/movie.js @@ -0,0 +1,53 @@ +"use strict"; + +function Movie() { + this.tableName = 'movies'; + this.columnNames = [ + 'id', // INTEGER PRIMARY KEY + 'title', // TEXT NOT NULL UNIQUE + 'overview', // TEXT + 'release_date', // TEXT + 'inventory' // INTEGER NOT NULL DEFAULT 0 + ]; +} + +// this is silly-ish, but necessary because of how we set up the DB object +Movie.prototype = require('./database').prototype; + +Movie.prototype.customersCurrent = function customersCurrent(movieTitle, callback) { + var db = this.openDB(); + var statement = 'SELECT customers.id AS customers_id, customers.name, rentals.id AS rental_id, rentals.checkout_date FROM customers INNER JOIN rentals ON customers.id = rentals.customer_id WHERE rentals.movie_title LIKE ? AND rentals.return_date = "";'; + var values = movieTitle; + + db.all(statement, values, function(err, rows) { + callback(err, rows); + db.close(); + }); +} + +Movie.prototype.customersPast = function customersPast(movieTitle, parameter, callback) { + var db = this.openDB(); + + // There is a security gap here with putting the parameter directly into the statement without a check that it is a valid sort parameter. + + var statement = 'SELECT customers.id AS customer_id, customers.name, rentals.id AS rental_id, rentals.checkout_date FROM customers INNER JOIN rentals ON customers.id = rentals.customer_id WHERE rentals.movie_title LIKE ? AND rentals.return_date != "" ORDER BY ' + parameter + ';'; + var values = [movieTitle]; + + db.all(statement, values, function(err, rows) { + callback(err, rows); + db.close(); + }); +} + +Movie.prototype.numAvail = function numAvail(movieTitle, callback) { + var statement = 'SELECT (SELECT inventory FROM movies WHERE title LIKE ?) - (SELECT COUNT(return_date) FROM rentals WHERE return_date = "" AND movie_title LIKE ?) AS num_available;' + var values = [movieTitle, movieTitle]; + + var db = this.openDB(); + db.all(statement, values, function(err, rows) { + callback(err, rows); + db.close(); + }); +} + +module.exports = Movie; diff --git a/models/rental.js b/models/rental.js new file mode 100644 index 0000000..0a238af --- /dev/null +++ b/models/rental.js @@ -0,0 +1,70 @@ +"use strict"; + +function Rental() { + this.tableName = "rentals"; + this.columnNames = [ + 'id', // INTEGER PRIMARY KEY + 'checkout_date', // TEXT NOT NULL DEFAULT CURRENT_DATE + 'return_date', // TEXT + 'movie_title', // TEXT NOT NULL // FOREIGN KEY(movie_title) REFERENCES movies(title) + 'customer_id' // INTEGER NOT NULL // FOREIGN KEY(customer_id) REFERENCES customers(id) + ]; +} + +Rental.prototype = require('./database').prototype; + +Rental.prototype.checkOut = function(data, callback) { + var db = this.openDB(); + var keys = Object.keys(data); + var questionMarks = []; + var values = []; + + for (var i = 0; i < keys.length; i++) { + values.push(data[keys[i]]); + questionMarks.push("?"); + } + + var insertStatement = 'INSERT INTO rentals (' + keys.join(', ') + ') VALUES (' + questionMarks.join(', ') + ');'; + var updateStatement = 'UPDATE customers SET account_balance = (account_balance - 250) WHERE id = ?;'; + db.serialize(function checkoutMovie() { + var res = {}; + db.run('BEGIN TRANSACTION;'); + + db.run(insertStatement, values, function updateRes(err) { + res.insertedRentalID = this.lastID; + res.changes = res.changes ? (res.changes + this.changes) : this.changes; + }); + + db.run(updateStatement, [data.customer_id], function updateRes(err) { + res.customerID = parseInt(data.customer_id); + res.changes = res.changes ? (res.changes + this.changes) : this.changes; + }); + + db.run('COMMIT TRANSACTION;', function createCallback(err) { + if (err) { + callback(err, { insertedID: null, changed: null }); + } else { + callback(err, res); + } + db.close(); + }); + }); +} + +Rental.prototype.checkIn = function(movie_title, date, customer_id, callback) { + var db = this.openDB(); + var statement = "UPDATE rentals SET return_date = ? WHERE movie_title LIKE ? AND customer_id = ?;"; + var values = [date, movie_title, customer_id]; + + db.run(statement, values, function(err) { + if (err) { + callback(err, { insertedID: null, changed: null }); + } else { + callback(err, { insertedID: this.lastID, changed: this.changes }); + } + + db.close(); + }); +} + +module.exports = Rental; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a559db8 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "C3Projects--VideoStoreAPI", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "nodemon ./bin/www", + "test": "DB=test mocha --recursive", + "db:schema": "node ./utils/schema.js", + "db:seed": "node ./utils/seed.js", + "db:reset": "npm run db:schema; npm run db:seed", + "db:test:reset": "DB=test npm run db:reset", + "sqlite": "npm run db:reset; sqlite3 db/development.db" + }, + "dependencies": { + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "express": "~4.13.1", + "morgan": "~1.6.1", + "sqlite3": "^3.1.0", + "supertest": "^1.1.0" + } +} diff --git a/routes/customers.js b/routes/customers.js new file mode 100644 index 0000000..542e15a --- /dev/null +++ b/routes/customers.js @@ -0,0 +1,10 @@ +"use strict"; + +var express = require('express'); +var router = express.Router(); +var Controller = require('../controllers/customers'); + +router.get('/', Controller.index); +router.get('/:id/rentals', Controller.rentals); + +module.exports = router; diff --git a/routes/movies.js b/routes/movies.js new file mode 100644 index 0000000..c7ea8c3 --- /dev/null +++ b/routes/movies.js @@ -0,0 +1,11 @@ +"use strict"; + +var express = require('express'); +var router = express.Router(); +var Controller = require('../controllers/movies'); + +router.get('/', Controller.index); +router.get('/:title', Controller.show); +router.get('/:title/customers', Controller.customers); + +module.exports = router; diff --git a/routes/rentals.js b/routes/rentals.js new file mode 100644 index 0000000..305fb27 --- /dev/null +++ b/routes/rentals.js @@ -0,0 +1,10 @@ +"use strict"; + +var express = require('express'); +var router = express.Router(); +var Controller = require('../controllers/rentals'); + +router.post('/', Controller.create); +router.put('/:customer_id', Controller.update); + +module.exports = router; diff --git a/test/controllers/customers_test.js b/test/controllers/customers_test.js new file mode 100644 index 0000000..8c0179d --- /dev/null +++ b/test/controllers/customers_test.js @@ -0,0 +1,452 @@ +"use strict"; + +var request = require('supertest'); +var assert = require('assert'); +var app = require('../../app'); +var agent = request.agent(app); + +var resetTables = require('../dbCleaner'); + +describe('/customers', function() { + describe("GET '/'", function() { + var numCustomersSeeded; + var request; + + before(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ] + } + numCustomersSeeded = data.customers.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/customers').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns all customers in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numCustomersSeeded); + done(); + }); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + }); + + describe("GET '?status=overdue'", function(){ + var request; + + before(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 3 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/customers?status=overdue').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns 2 customers in the body', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + done(); + }); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'customer_id', 'name', 'movie_title', 'checkout_date', 'rental_id' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + }); + + describe("GET '?sort'", function() { + before(function(done) { + var data = { + customers: [ + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer4', registered_at: '2013-01-25', address: 'Address4', city: 'City4', state: 'State4', postal_code: 'Zip4', phone: 'Phone4', account_balance: '0050' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: '3Zip3', phone: 'Phone3', account_balance: '3000' } + ] + } + resetTables(data, done); + }); + + describe("GET '?sort=registered_at'", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=registered_at').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + + it('returns all customers, sorted by when they registered', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 4); + assert.equal(res.body[0].name, 'Customer4'); + assert.equal(res.body[1].name, 'Customer3'); + assert.equal(res.body[2].name, 'Customer2'); + assert.equal(res.body[3].name, 'Customer1'); + done(); + }); + }); + }); + + describe("GET '?sort=postal_code'", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=postal_code').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + + it('returns all customers, sorted by postal code', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 4); + assert.equal(res.body[0].name, 'Customer3'); + assert.equal(res.body[1].name, 'Customer1'); + assert.equal(res.body[2].name, 'Customer2'); + assert.equal(res.body[3].name, 'Customer4'); + done(); + }); + }); + }); + + describe("GET '?sort=name'", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=name').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + + it('returns all customers, sorted by name', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 4); + assert.equal(res.body[0].name, 'Customer1'); + assert.equal(res.body[1].name, 'Customer2'); + assert.equal(res.body[2].name, 'Customer3'); + assert.equal(res.body[3].name, 'Customer4'); + done(); + }); + }); + }); + + describe("GET '?sort=name&n=2&p=2'", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=name&n=2&p=2').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + + it('only returns the correct 2 customers in the body', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + assert.equal(res.body[0].name, 'Customer3'); + assert.equal(res.body[1].name, 'Customer4'); + done(); + }); + }); + }); + + describe("GET '?sort=name&n=2&p=cat' (invalid p)", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=name&n=2&p=cat').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an array of customer objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_balance' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + }); + }); + + it('only returns the correct 2 customers in the body (treats p as 1)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + assert.equal(res.body[0].name, 'Customer1'); + assert.equal(res.body[1].name, 'Customer2'); + done(); + }); + }); + }); + + describe("GET '?sort=name&n=cat' (invalid n)", function() { + var request; + var expectedResponse; + + before(function(done) { + agent.get('/customers?sort=name').set('Accept', 'application/json') + .end(function(err, res) { + expectedResponse = res; + done(); + }); + }); + + beforeEach(function() { + request = agent.get('/customers?sort=name&n=cat').set('Accept', 'application/json'); + }); + + it('is equivalent to not including n', function(done) { + request + .expect(200, function(err, res) { + assert.deepEqual(res.body, expectedResponse.body); + done(); + }); + }); + }); + + describe("GET '?sort=name&p=2' (missing n, includes p)", function() { + var request; + var expectedResponse; + + before(function(done) { + agent.get('/customers?sort=name&p=2').set('Accept', 'application/json') + .end(function(err, res) { + expectedResponse = res; + done(); + }); + }); + + beforeEach(function() { + request = agent.get('/customers?sort=name&n=cat').set('Accept', 'application/json'); + }); + + it('is equivalent just including sort, without p or n', function(done) { + request + .expect(200, function(err, res) { + assert.deepEqual(res.body, expectedResponse.body); + done(); + }); + }); + }); + + describe("GET '?sort=name&n=cat&p=2' (invalid n, includes p)", function() { + var request; + var expectedResponse; + + before(function(done) { + agent.get('/customers?sort=name&n=cat&p=2').set('Accept', 'application/json') + .end(function(err, res) { + expectedResponse = res; + done(); + }); + }); + + beforeEach(function() { + request = agent.get('/customers?sort=name&n=cat').set('Accept', 'application/json'); + }); + + it('is equivalent just including sort, without p or n', function(done) { + request + .expect(200, function(err, res) { + assert.deepEqual(res.body, expectedResponse.body); + done(); + }); + }); + }); + + describe("GET '?sort=puppies' (invalid sort parameter)", function() { + var request; + + beforeEach(function() { + request = agent.get('/customers?sort=puppies').set('Accept', 'application/json'); + }); + + it('responds with json, status code 400, and appropriate error message', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(400, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.error.text, '"Bad request"'); + done(); + }); + }); + }); + }); + + describe("GET '/:id/rentals", function() { + var err; + var res; + + before(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 1 }, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 1 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 2 } + ] + } + resetTables(data, done); + }); + + before(function(done) { + agent.get('/customers/1/rentals').set('Accept', 'application/json').end(function(error, result) { + err = error; + res = result; + done(); + }); + }); + + it('returns JSON', function() { + assert.equal(err, undefined); + assert.equal(res.status, 200); + }); + + it('includes an object with attributes: past and current', function() { + assert.equal(res.body.past.length, 1); + assert.equal(res.body.current.length, 1); + }); + + it('past includes rentals with a return date', function() { + assert.equal(res.body.past[0].return_date, '2015-03-20'); + }); + + it('current includes rentals without a return date', function() { + assert.equal(res.body.current[0].return_date, ''); + }); + + it('all rentals have a rental id', function() { + assert.equal(res.body.current[0].id, 2); + assert.equal(res.body.past[0].id, 1); + }); + }); +}); diff --git a/test/controllers/movies_test.js b/test/controllers/movies_test.js new file mode 100644 index 0000000..b32d402 --- /dev/null +++ b/test/controllers/movies_test.js @@ -0,0 +1,688 @@ +"use strict"; + +var request = require('supertest'); +var assert = require('assert'); +var app = require('../../app'); +var agent = request.agent(app); + +var resetTables = require('../dbCleaner'); + +describe('/movies', function() { + describe("GET '/'", function() { + var numMoviesSeeded; + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + numMoviesSeeded = data.movies.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns all movies in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numMoviesSeeded); + done(); + } + ); + }); + + it('returns an array of movie objects (with the appropriate keys)', function(done) { + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + + var keys = [ 'id', 'title', 'overview', 'release_date', 'inventory' ]; + assert.deepEqual(Object.keys(res.body[0]), keys); + done(); + } + ); + }); + }); + + describe("GET '?sort=title'", function() { + var numMoviesSeeded; + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + numMoviesSeeded = data.movies.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=title').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns all movies sorted by title in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numMoviesSeeded); + assert.equal(res.body[0].title, 'Jaws'); + assert.equal(res.body[1].title, 'Jaws and Maws'); + assert.equal(res.body[2].title, 'The French Connection'); + done(); + } + ); + }); + }); + + describe("GET '?sort=release_date'", function() { + var numMoviesSeeded; + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + numMoviesSeeded = data.movies.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=release_date').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns all movies sorted by release_date in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numMoviesSeeded); + assert.equal(res.body[0].release_date, '1971-10-07'); + assert.equal(res.body[1].release_date, '1975-06-19'); + assert.equal(res.body[2].release_date, '2015-09-12'); + done(); + } + ); + }); + }); + + describe("GET '?sort=puppies' (sort parameter is invalid)", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=puppies').set('Accept', 'application/json'); + }); + + it('responds with json and a status code of 400', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(400, done); + }); + + it('returns a status code of 400 and an error message', function(done){ + request + .expect(400, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.error.status, 400); + assert.equal(res.error.text, '"Bad request"'); + done(); + } + ); + }); + }); + + describe("GET '?sort=release_date&n=2'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=release_date&n=2').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns 2 movies sorted by release_date in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + assert.equal(res.body[0].release_date, '1971-10-07'); + assert.equal(res.body[1].release_date, '1975-06-19'); + done(); + } + ); + }); + }); + + describe("GET '?sort=release_date&n=dog' (n is invalid)", function() { + var numMoviesSeeded; + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + numMoviesSeeded = data.movies.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=release_date&n=dog').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('treats n as null and returns all movies sorted by release_date in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numMoviesSeeded); + assert.equal(res.body[0].release_date, '1971-10-07'); + done(); + } + ); + }); + }); + + describe("GET '?sort=title&n=1&p=2'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=title&n=1&p=2').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns 1 movie sorted by title from the second page in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 1); + assert.equal(res.body[0].title, 'Jaws and Maws'); + done(); + } + ); + }); + }); + + describe("GET '?sort=title&n=2&p=dog' (p is invalid)", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=title&n=2&p=dog').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('treats p as null and returns 2 movies sorted by title in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + assert.equal(res.body[0].title, 'Jaws'); + assert.equal(res.body[1].title, 'Jaws and Maws'); + done(); + } + ); + }); + }); + + describe("GET '?sort=title&p=3' (n is missing)", function() { + var numMoviesSeeded; + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + numMoviesSeeded = data.movies.length; + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies?sort=title&p=dog').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('treats p as null and returns all movies sorted by title in the body', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, numMoviesSeeded); + assert.equal(res.body[0].title, 'Jaws'); + assert.equal(res.body[1].title, 'Jaws and Maws'); + assert.equal(res.body[2].title, 'The French Connection'); + done(); + } + ); + }); + }); + + describe("GET '/:title'", function() { + describe("GET '/Jaws'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: '3Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Jaws', customer_id: 9 }, + { checkout_date: '2015-08-24', return_date: '2015-09-04', movie_title: 'Jaws', customer_id: 2 }, + { checkout_date: '2015-06-12', return_date: '', movie_title: 'Jaws and Maws', customer_id: 2 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/Jaws').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns a single movie object with a title of Jaws', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.id, 2); + assert.equal(res.body.title, 'Jaws'); + assert.equal(res.body.overview, 'Shark!'); + assert.equal(res.body.release_date, '1975-06-19'); + assert.equal(res.body.inventory, 10); + assert.equal(res.body.num_available, 9); + done(); + } + ); + }); + }); + + describe("GET '/JAWS'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/JAWS').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns a single movie object with a title of Jaws', function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.id, 2); + assert.equal(res.body.title, 'Jaws'); + assert.equal(res.body.overview, 'Shark!'); + assert.equal(res.body.release_date, '1975-06-19'); + assert.equal(res.body.inventory, 10); + done(); + } + ); + }); + }); + + describe.skip("GET '/dog' (movie does not exist)", function() { // TODO: How to handle this error? + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/dog').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it('returns an empty array', function(done){ + request + .expect(200, function(err, res) { + console.log(err); + console.log(res.body); + + done(); + } + ); + }); + }); + }); + + describe("GET '/:title/customers?status=current'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 3 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/Jaws/customers?status=current').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it("returns the 1 customer that currently has 'Jaws' checked out", function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 1); + assert.equal(res.body[0].customers_id, 1); + assert.equal(res.body[0].name, 'Customer1'); + assert.equal(res.body[0].rental_id, 3); + assert.equal(res.body[0].checkout_date, '2015-08-10'); + done(); + } + ); + }); + }); + + describe("GET '/:title/customers?status=past&sort=customer_id'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 3 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/North%20by%20Northwest/customers?status=past&sort=customer_id').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it("returns the 1 customer that has checked out 'North by Northwest' in the past", function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 1); + assert.equal(res.body[0].customer_id, 2); + assert.equal(res.body[0].name, 'Customer2'); + assert.equal(res.body[0].rental_id, 1); + assert.equal(res.body[0].checkout_date, '2015-03-16'); + done(); + } + ); + }); + }); + + describe("GET '/:title/customers?status=dog'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 3 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/Jaws/customers?status=dog').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(400, done); + }); + + it("returns a status code of 400 and an error message of 'Bad request'", function(done){ + request + .expect(400, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.error.status, 400); + assert.equal(res.error.text, '"Bad request"'); + done(); + } + ); + }); + }); + + describe("GET '/:title/customers?status=past&sort=name'", function() { + var request; + + before(function(done) { + var data = { + movies: [ + { title: 'Wait Until Dark', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'North by Northwest', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-02-20', return_date: '2015-02-23', movie_title: 'North by Northwest', customer_id: 1}, + { checkout_date: '2015-09-09', return_date: '', movie_title: 'Wait Until Dark', customer_id: 3 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + resetTables(data, done); + }); + + beforeEach(function() { + request = agent.get('/movies/North%20by%20Northwest/customers?status=past&sort=name').set('Accept', 'application/json'); + }); + + it('responds with json', function(done) { + request + .expect('Content-Type', /application\/json/) + .expect(200, done); + }); + + it("returns the 2 customers that have checked out 'North by Northwest' in the past sorted by name", function(done){ + request + .expect(200, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.body.length, 2); + assert.equal(res.body[0].customer_id, 1); + assert.equal(res.body[0].name, 'Customer1'); + assert.equal(res.body[0].rental_id, 2); + assert.equal(res.body[0].checkout_date, '2015-02-20'); + assert.equal(res.body[1].customer_id, 2); + assert.equal(res.body[1].name, 'Customer2'); + assert.equal(res.body[1].rental_id, 1); + assert.equal(res.body[1].checkout_date, '2015-03-16'); + done(); + } + ); + }); + }); +}); diff --git a/test/controllers/rentals_test.js b/test/controllers/rentals_test.js new file mode 100644 index 0000000..15b6e59 --- /dev/null +++ b/test/controllers/rentals_test.js @@ -0,0 +1,88 @@ +"use strict"; + +var request = require('supertest'); +var assert = require('assert'); +var app = require('../../app'); +var agent = request.agent(app); +var Rental = require('../../models/rental'); + +var resetTables = require('../dbCleaner'); + +describe('/rentals', function() { + describe("POST '/'", function() { + var err; + var res; + + before(function(done) { + var data = { + customers: [ { name: 'Customer1', account_balance: 500 } ], + movies: [ { title: 'Movie1', inventory: 1 } ] + } + resetTables(data, done) + }); + + before(function(done) { + agent.post('/rentals') + .send({ checkout_date: '2015-03-16', movie_title: 'Movie1', customer_id: '1' }) + .expect(200, function(error, response) { + err = error; + res = response; + done(); + }); + }); + + it('adds a record to the db, with all of the correct data', function(done) { + new Rental().all(function(error, rows) { + assert.equal(error, undefined); + assert.equal(rows.length, 1); + done(); + }); + }); + + it('returns a confirmation that the request was successful', function() { + assert.equal(err, undefined); + assert.equal(res.body.insertedRentalID, 1); + assert.equal(res.body.changes, 2); + }); + }); + + describe("PUT '/2'", function() { + var rental = new Rental; + var request; + + beforeEach(function(done) { + var data = { + rentals: [ + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Wait Until Dark', customer_id: 9 }, + { checkout_date: '2015-08-24', return_date: '', movie_title: 'Wait Until Dark', customer_id: 2 }, + ] + } + resetTables(data, done); + }); + + beforeEach(function(done) { + request = agent.put('/rentals/2').set('Content-Type', 'application/json') + .send({'return_date':'2015-09-23'}) + .send({'movie_title':'Wait Until Dark'}) + .end(done); + }); + + it('updates the rental to have a return_date', function(done) { + request + rental.findBy('customer_id', 2, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].customer_id, 2) + assert.equal(rows[0].return_date, '2015-09-23'); + + rental.findBy('customer_id', 9, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].customer_id, 9); + assert.equal(rows[0].return_date, ''); + done(); + }); + }); + }); + }); +}); diff --git a/test/dbCleaner.js b/test/dbCleaner.js new file mode 100644 index 0000000..f8c4b8b --- /dev/null +++ b/test/dbCleaner.js @@ -0,0 +1,114 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development'; + +// // example data + // { + // movies: [ + // { title: 'Movie1', overview: 'Descr1', release_date: '1975-06-19', inventory: 10 }, + // { title: 'Movie2', overview: 'Descr2', release_date: 'Yesterday', inventory: 11 }, + // { title: 'Movie3', overview: 'Descr3', release_date: 'Yesterday', inventory: 11 } + // ], + // customers: [ + // { name: 'Customer1', registered_at: '01/02/2015', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + // { name: 'Customer2', registered_at: '12/01/2014', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' } + // ], + // rentals: [ + // { checkout_date: '2015-09-16', return_date: '', movie_title: 'Movie1', customer_id: 1 }, + // { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'Movie2', customer_id: 1 }, + // { checkout_date: '2015-09-18', return_date: '', movie_title: 'Movie3', customer_id: 2 } + // ] + // } + +function resetAllTables(data, done) { + var db = new sqlite3.Database('db/' + db_env + '.db'); + + var movies = data.movies; + var customers = data.customers; + var rentals = data.rentals; + + db.serialize(function resetTables() { + db.exec('BEGIN TRANSACTION;'); + + db.exec('DELETE FROM movies;'); + + // NOTE: movies, customers, and rentals are all optional. Can seed just one, or all three. + if (movies) { + var statement = db.prepare( + 'INSERT INTO movies (title, overview, release_date, inventory) \ + VALUES (?, ?, ?, ?);' + ); + + for (var i = 0; i < movies.length; i++) { + var movie = movies[i]; + + statement.run( + movie.title, + movie.overview, + movie.release_date, + movie.inventory + ); + } + + statement.finalize(); + } + + db.exec('DELETE FROM customers;'); + + if (customers) { + var statement = db.prepare( + 'INSERT INTO customers (name, registered_at, address, city, state, postal_code, phone, account_balance) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?);' + ); + + for (var i = 0; i < customers.length; i++) { + var customer = customers[i]; + + statement.run( + customer.name, + customer.registered_at, + customer.address, + customer.city, + customer.state, + customer.postal_code, + customer.phone, + customer.account_balance + ); + } + + statement.finalize(); + } + + db.exec('DELETE FROM rentals;'); + + if (rentals) { + db.exec('PRAGMA defer_foreign_keys = ON'); + + var statement = db.prepare( + 'INSERT INTO rentals (checkout_date, return_date, movie_title, customer_id) \ + VALUES (?, ?, ?, ?);' + ); + + for (var i = 0; i < rentals.length; i++) { + var rental = rentals[i]; + + statement.run( + rental.checkout_date, + rental.return_date, + rental.movie_title, + rental.customer_id + ); + } + + statement.finalize(); + } + + db.exec('COMMIT TRANSACTION;', function(err) { + db.close(); + done(err); + }); + }); +} + +module.exports = resetAllTables; diff --git a/test/models/customer_test.js b/test/models/customer_test.js new file mode 100644 index 0000000..9065bbc --- /dev/null +++ b/test/models/customer_test.js @@ -0,0 +1,292 @@ +"use strict"; + +var assert = require("assert"); +var Customer = require('../../models/customer'); +var resetTables = require('../dbCleaner'); + +describe('Customer', function() { + var customer = new Customer(); + + it('can be instantiated', function() { + assert.equal(customer instanceof Customer, true); + }); + + it('holds onto the `path` to the database', function() { + assert.equal(customer.dbPath(), "db/test.db"); + }); + + describe('#create', function() { + function validCustomerData() { + return { + name: 'Customer1', + registered_at: 'Yesterday', + address: '1234 Nowhere St', + city: 'Nowhereville', + state: 'NW', + postal_code: '12345', + phone: '123-456-7890', + account_balance: '2045' + }; + }; + + beforeEach(function(done) { + resetTables({}, done) + }); + + it('creates a new customer record', function(done) { + var data = validCustomerData(); + + customer.create(data, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.insertedID, 1); + assert.equal(res.changed, 1); + done(); + }); + }); + + it('requires at least one input', function(done) { + var data = {} + + customer.create(data, function(err, res) { + // err = { [Error: SQLITE_ERROR: near ")": syntax error] errno: 1, code: 'SQLITE_ERROR' } + assert.equal(err.errno, 1); + done(); + }); + }); + + it('requires a name', function(done) { + var data = validCustomerData(); + delete data.name; + + customer.create(data, function(err, res) { + // err = { [Error: SQLITE_CONSTRAINT: NOT NULL constraint failed: customers.name] errno: 19, code: 'SQLITE_CONSTRAINT' } + assert.equal(err.errno, 19); + assert.equal(err.message, 'SQLITE_CONSTRAINT: NOT NULL constraint failed: customers.name'); + done(); + }); + }); + + it('defaults account balance to zero', function(done) { + var data = validCustomerData(); + delete data.account_balance; + + customer.create(data, function(err, res) { + assert.equal(err, undefined); + assert(res.insertedID, 1); + customer.findBy('id', 1, function(err, rows) { + assert.equal(rows.length, 1); + assert.equal(rows[0].account_balance, 0); + done(); + }); + }); + }); + }); + + describe('#all', function() { + var numCustomersSeeded; + + beforeEach(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ] + } + + numCustomersSeeded = data.customers.length; + + resetTables(data, done); + }); + + it('returns all customers', function(done) { + customer.all(function(err, rows){ + assert.equal(err, undefined); + assert.equal(rows.length, numCustomersSeeded); + done(); + }); + }); + }); + + describe('#findBy', function() { + var numCustomersSeeded; + + beforeEach(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ] + } + + numCustomersSeeded = data.customers.length; + + resetTables(data, done); + }); + + // because of how we seeded the db, this also tests that it will only return exact title matches + it('returns 1 customer where the name is Customer1', function(done) { + customer.findBy('name', 'Customer1', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].name, 'Customer1'); + done(); + }); + }); + + it('"CUSTOMER1" returns customer with name "Customer1"', function(done) { + customer.findBy('name', 'CUSTOMER1', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].name, 'Customer1'); + done(); + }); + }); + + it('does not return partial patches', function(done) { + customer.findBy('name', 'Customer', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 0); + done(); + }); + }); + + it('returns an error when an unrecognized column is provided', function(done) { + customer.findBy('badColumnName', 'Jaws', function(err, rows) { + assert(err); + assert.equal(err.message, 'Error: syntax error. Unrecognized parameter.'); + assert.equal(rows, undefined); + done(); + }); + }); + }); + + describe('#sortBy', function() { + var numCustomersSeeded; + + beforeEach(function(done) { + var data = { + customers: [ + // intentionally out of order in order to test sorting + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' }, + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' } + ] + } + + numCustomersSeeded = data.customers.length; + + resetTables(data, done); + }); + + it('returns all customers sorted by name', function(done) { + customer.sortBy('name', null, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, numCustomersSeeded); + assert.equal(rows[0].name, 'Customer1'); + done(); + }); + }); + + it('returns 1 customer sorted by postal_code', function(done) { + customer.sortBy('postal_code', 1, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].postal_code, 'Zip1'); + done(); + }); + }); + + it('returns all customers sorted by registered_at', function(done) { + customer.sortBy('registered_at', null, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, numCustomersSeeded); + assert.equal(rows[0].registered_at, '2014-01-25'); + done(); + }); + }); + + it('returns 1 customer sorted by name from the second page', function(done) { + customer.sortBy('name', 1, 2, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].name, 'Customer2'); + done(); + }); + }); + + it('returns an error when an unrecognized column is provided', function(done) { + customer.sortBy('badColumnName', null, null, function(err, rows) { + assert(err); + assert.equal(err.message, 'Bad request'); + assert.equal(rows, undefined); + done(); + }); + }); + }); + + describe('#rentals', function() { + before(function(done) { + var data = { + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + { name: 'Customer3', registered_at: '2014-01-25', address: 'Address3', city: 'City3', state: 'State3', postal_code: 'Zip3', phone: 'Phone3', account_balance: '3000' } + ], + movies: [ + { title: 'Movie1', overview: 'Descr1', release_date: '1975-06-19', inventory: 10 }, + { title: 'Movie2', overview: 'Descr2', release_date: '2005-05-12', inventory: 11 }, + { title: 'Movie3', overview: 'Descr3', release_date: '2012-10-16', inventory: 11 }, + { title: 'Movie4', overview: 'Descr4', release_date: '1985-03-22', inventory: 7 } + ], + rentals: [ + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Movie1', customer_id: 1 }, + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'Movie2', customer_id: 1 }, + { checkout_date: '2015-09-18', return_date: '', movie_title: 'Movie3', customer_id: 2 }, + { checkout_date: '2015-02-23', return_date: '', movie_title: 'Movie4', customer_id: 2 } + ] + } + + resetTables(data, done); + }); + + it('returns all rentals for a given customer, in order by checkout date', function(done) { + customer.rentals(1, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 2); + assert.equal(rows[0].movie_title, "Movie2"); + assert.equal(rows[1].movie_title, "Movie1"); + done(); + }); + }); + }); + + describe('#overdue', function() { + it('returns a list of customers with overdue movies', function(done) { + customer.overdue(function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 3); + assert.equal(rows[0].name, 'Customer1'); + assert.equal(rows[0].movie_title, 'Movie1'); + assert.equal(rows[0].checkout_date, '2015-09-16'); + done(); + }); + }); + }); + + // describe('#movies', function() { + // before(function(done) { + // seedRentals(done); // this has been deprecated + // }); + + // it('returns all movies the customer has/had checked out', function(done) { + // customer.movies(1, function(err, rows) { + // assert.equal(rows.length, 2); + // assert.equal(rows[0].title, "Movie1"); + // assert.equal(rows[1].title, "Movie2"); + // done(); + // }); + // }); + // }); +}); diff --git a/test/models/database_test.js b/test/models/database_test.js new file mode 100644 index 0000000..6d74dd7 --- /dev/null +++ b/test/models/database_test.js @@ -0,0 +1,22 @@ +"use strict"; + +var assert = require("assert"); +var Database = require('../../models/database'); + +describe("Database", function() { + var db; + var dbPath = "db/test.db"; + + beforeEach(function(done) { + db = new Database(); + done(); + }); + + it("can be instantiated", function() { + assert.equal(db instanceof Database, true); + }); + + it("holds onto the `path` to the database", function() { + assert.equal(db.dbPath(), dbPath); + }); +}); diff --git a/test/models/movie_test.js b/test/models/movie_test.js new file mode 100644 index 0000000..54d7289 --- /dev/null +++ b/test/models/movie_test.js @@ -0,0 +1,294 @@ +"use strict"; + +var assert = require("assert"); +var Movie = require('../../models/movie'); +var resetTables = require('../dbCleaner'); + +describe('Movie', function() { + var movie = new Movie(); + + it("can be instantiated", function() { + assert(movie instanceof Movie); + }); + + it("holds onto the `path` to the database", function() { + assert.equal(movie.dbPath(), "db/test.db"); + }); + + describe('#create', function() { + function validMovieData() { + return { + title: 'RoboJaws', + overview: 'Jaws is hunted by RoboJaws', + release_date: 'Tomorrow', + inventory: 10 + }; + }; + + beforeEach(function(done) { + resetTables({}, done); + }); + + it('creates a new movie record', function(done) { + var data = validMovieData(); + + movie.create(data, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.insertedID, 1); + assert.equal(res.changed, 1); + done(); + }); + }); + + it('requires a title', function(done) { + var data = validMovieData(); + delete data.title; + + movie.create(data, function(err, res) { + assert.equal(err.errno, 19); + assert.equal(err.message, 'SQLITE_CONSTRAINT: NOT NULL constraint failed: movies.title'); + done(); + }); + }); + + it('requires a title to be unique', function(done) { + var data = validMovieData(); + + movie.create(data, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.insertedID, 1); + assert.equal(res.changed, 1); + + movie.create(data, function(err, res) { + assert.equal(err.errno, 19); + assert.equal(err.message, 'SQLITE_CONSTRAINT: UNIQUE constraint failed: movies.title'); + done(); + }); + }); + }); + + it('defaults inventory to zero', function(done) { + var data = validMovieData(); + delete data.inventory; + + movie.create(data, function(err, res) { + assert.equal(err, undefined); + assert(res.insertedID, 1); + movie.findBy('id', res.insertedID, function(err, rows) { + assert.equal(rows.length, 1); + assert.equal(rows[0].inventory, 0); + done(); + }); + }); + }); + }); + + describe('#all', function() { + var numMoviesSeeded; + + beforeEach(function(done) { + var data = { + movies: [ + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + + numMoviesSeeded = data.movies.length; + + resetTables(data, done); + }); + + it('returns all movies', function(done) { + movie.all(function(err, rows){ + assert.equal(err, undefined); + assert.equal(rows.length, numMoviesSeeded); + done(); + }); + }); + }); + + describe('#findBy', function() { + beforeEach(function(done) { + var data = { + movies: [ + // NOTE: we need to maintain these titles (where 'Jaws' is in both) + // in order to test that only exact matches are returned + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ] + } + + resetTables(data, done); + }); + + // because of how we seeded the db, this also tests that it will only return exact title matches + it('returns 1 movie where the name is Jaws', function(done) { + movie.findBy('title', 'Jaws', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].title, 'Jaws'); + done(); + }); + }); + + it('"JAWS" returns movie with title "Jaws"', function(done) { + movie.findBy('title', 'JAWS', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].title, 'Jaws'); + done(); + }); + }); + + it('returns an error when an unrecognized column is provided', function(done) { + movie.findBy('badColumnName', 'Jaws', function(err, rows) { + assert(err); + assert.equal(err.message, 'Error: syntax error. Unrecognized parameter.'); + assert.equal(rows, undefined); + done(); + }); + }); + }); + + describe('#sortBy', function() { + var numMoviesSeeded; + + beforeEach(function(done) { + var data = { + movies: [ + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '1980-01-01', inventory: 11 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 }, + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 } + ] + } + + numMoviesSeeded = data.movies.length; + + resetTables(data, done); + }); + + it('returns all movies sorted by title', function(done) { + movie.sortBy('title', null, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, numMoviesSeeded); + assert.equal(rows[0].title, 'Jaws'); + done(); + }); + }); + + it('returns all movies sorted by release_date', function(done){ + movie.sortBy('release_date', null, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, numMoviesSeeded); + assert.equal(rows[0].release_date, '1971-10-07'); + done(); + }); + }); + + it('returns 1 movie sorted by title', function(done) { + movie.sortBy('title', 1, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].title, 'Jaws'); + done(); + }); + }); + + it('returns 1 movie sorted by release_date', function(done){ + movie.sortBy('release_date', 1, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].release_date, '1971-10-07'); + done(); + }); + }); + + it('returns 1 movie sorted by title from the second page', function(done) { + movie.sortBy('title', 1, 2, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].title, 'Jaws and Maws'); + done(); + }); + }); + }); + + describe('Movie specific functions', function() { + beforeEach(function(done) { + var data = { + movies: [ + { title: 'Jaws', overview: 'Shark!', release_date: '1975-06-19', inventory: 10 }, + { title: 'Jaws and Maws', overview: 'Worm!', release_date: '2015-09-12', inventory: 11 }, + { title: 'The French Connection', overview: 'Bonjour!', release_date: '1971-10-07', inventory: 8 } + ], + customers: [ + { name: 'Customer1', registered_at: '2015-01-02', address: 'Address1', city: 'City1', state: 'State1', postal_code: 'Zip1', phone: 'Phone1', account_balance: '1250' }, + { name: 'Customer2', registered_at: '2014-12-01', address: 'Address2', city: 'City2', state: 'State2', postal_code: 'Zip2', phone: 'Phone2', account_balance: '1000' }, + ], + rentals: [ + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Jaws', customer_id: 1 }, + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'Jaws and Maws', customer_id: 1 }, + { checkout_date: '2015-06-23', return_date: '', movie_title: 'Jaws and Maws', customer_id: 2 }, + { checkout_date: '2015-09-18', return_date: '', movie_title: 'The French Connection', customer_id: 2 } + ] + } + + resetTables(data, done); + }); + + describe('#customersCurrent', function() { + it('returns a list of customers who currently have checked out a movie given the title', function(done) { + movie.customersCurrent('The French Connection', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].rental_id, 4); + assert.equal(rows[0].checkout_date, '2015-09-18'); + done(); + }); + }); + }); + + describe('#customersPast', function() { + it('returns a list of customers sorted by customer_id who have checked out a movie in the past given the title', function(done) { + movie.customersPast('Jaws and Maws', 'customer_id', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].customer_id, 1); + done(); + }); + }); + + it('returns a list of customers sorted by customer name who have checked out a movie in the past given the title', function(done) { + movie.customersPast('Jaws and Maws', 'name', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].name, 'Customer1'); + done(); + }); + }); + + it('returns a list of customers sorted by checkout_date who have checked out a movie in the past given the title', function(done) { + movie.customersPast('Jaws and Maws', 'checkout_date', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].checkout_date, '2015-03-16'); + done(); + }); + }); + }); + + describe('#numAvail', function() { + it('returns the number of movies available for rent for a given movie', function(done) { + movie.numAvail('Jaws and Maws', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].num_available, 10); + done(); + }); + }); + }); + }); +}); diff --git a/test/models/rental_test.js b/test/models/rental_test.js new file mode 100644 index 0000000..97daf8d --- /dev/null +++ b/test/models/rental_test.js @@ -0,0 +1,295 @@ +"use strict"; + +var assert = require("assert"); +var Rental = require('../../models/rental'); +var Customer = require('../../models/customer'); +var resetTables = require('../dbCleaner'); + +describe('Rental', function() { + var rental = new Rental(); + + it("can be instantiated", function() { + assert(rental instanceof Rental); + }); + + it("holds onto the `path` to the database", function() { + assert.equal(rental.dbPath(), "db/test.db"); + }); + + describe('#create', function() { + function validRentalData() { + return { + checkout_date: '2014-12-16', + return_date: '', + movie_title: 'The Great Escape', + customer_id: 15 + }; + }; + + beforeEach(function(done) { + resetTables({}, done); + }); + + it('creates a new rental record', function(done) { + var data = validRentalData(); + + rental.create(data, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.insertedID, 1); + assert.equal(res.changed, 1); + done(); + }); + }); + + it('defaults checkout_date to current date', function(done) { + var data = validRentalData(); + var date = new Date(), + year = date.getFullYear(), + month = addZero(date.getMonth() + 1), + day = addZero(date.getDate()); + + function addZero(unit) { + unit = unit < 10 ? "0" + unit : unit; + return unit; + } + + var current_date = year + "-" + month + "-" + day; + delete data.checkout_date; + + rental.create(data, function(err, res) { + assert.equal(err, undefined); + assert(res.insertedID, 1); + + rental.findBy('movie_title', data.movie_title, function(err, rows) { + assert.equal(rows.length, 1); + assert.equal(rows[0].checkout_date, current_date); + done(); + }); + }); + }); + + it('requires a movie_title', function(done) { + var data = validRentalData(); + delete data.movie_title; + + rental.create(data, function(err, res) { + assert.equal(err.errno, 19); + assert.equal(err.message, 'SQLITE_CONSTRAINT: NOT NULL constraint failed: rentals.movie_title'); + done(); + }); + }); + + it('requires a customer_id', function(done) { + var data = validRentalData(); + delete data.customer_id; + + rental.create(data, function(err, res) { + assert.equal(err.errno, 19); + assert.equal(err.message, 'SQLITE_CONSTRAINT: NOT NULL constraint failed: rentals.customer_id'); + done(); + }); + }); + }); + + describe('#all', function() { + var numRentalsSeeded; + + beforeEach(function(done) { + var data = { + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Wait Until Dark', customer_id: 9 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + + numRentalsSeeded = data.rentals.length; + + resetTables(data, done); + }); + + it('returns all rentals', function(done) { + rental.all(function(err, rows){ + assert.equal(err, undefined); + assert.equal(rows.length, numRentalsSeeded); + done(); + }); + }); + }); + + describe('#findBy', function() { + beforeEach(function(done) { + var data = { + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Wait Until Dark', customer_id: 9 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + + resetTables(data, done); + }); + + it("returns 1 rental where the movie_title is 'Wait Until Dark'", function(done) { + rental.findBy('movie_title', 'Wait Until Dark', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].movie_title, 'Wait Until Dark'); + done(); + }); + }); + + it("'WAIT UNTIL dark' returns 1 rental where the movie_title is 'Wait Until Dark'", function(done) { + rental.findBy('movie_title', 'WAIT UNTIL dark', function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].movie_title, 'Wait Until Dark'); + done(); + }); + }); + + it('returns all rentals where the return_date is an empty string', function(done) { + rental.findBy('return_date', "", function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 2); + assert.equal(rows[0].movie_title, 'Wait Until Dark'); + assert.equal(rows[1].movie_title, 'Jaws'); + done(); + }); + }); + + it('returns an error when an unrecognized column is provided', function(done) { + rental.findBy('badColumnName', 'North by Northwest', function(err, rows) { + assert(err); + assert.equal(err.message, 'Error: syntax error. Unrecognized parameter.'); + assert.equal(rows, undefined); + done(); + }); + }); + }); + + describe('#sortBy', function() { + var numRentalsSeeded; + + beforeEach(function(done) { + var data = { + rentals: [ + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Wait Until Dark', customer_id: 9 }, + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + + numRentalsSeeded = data.rentals.length; + + resetTables(data, done); + }); + + it('returns all rentals sorted by return_date', function(done) { + rental.sortBy('return_date', null, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, numRentalsSeeded); + assert.equal(rows[0].return_date, ""); + assert.equal(rows[0].movie_title, 'Wait Until Dark'); + done(); + }); + }); + + it('returns 1 rental sorted by customer_id', function(done) { + rental.sortBy('customer_id', 1, null, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].id, 3) + assert.equal(rows[0].customer_id, 1); + done(); + }); + }); + + it('returns 1 rental sorted by customer_id from the second page', function(done) { + rental.sortBy('customer_id', 1, 2, function(err, rows) { + assert.equal(err, undefined); + assert.equal(rows.length, 1); + assert.equal(rows[0].customer_id, 2); + done(); + }); + }); + }); + + describe('#checkIn', function() { + beforeEach(function(done) { + var data = { + rentals: [ + { checkout_date: '2015-03-16', return_date: '2015-03-20', movie_title: 'North by Northwest', customer_id: 2 }, + { checkout_date: '2015-09-16', return_date: '', movie_title: 'Wait Until Dark', customer_id: 9 }, + { checkout_date: '2015-08-23', return_date: '', movie_title: 'Wait Until Dark', customer_id: 4}, + { checkout_date: '2015-08-10', return_date: '', movie_title: 'Jaws', customer_id: 1 } + ] + } + + resetTables(data, done); + }); + + it('checks in a rental by adding a return date', function(done) { + var movie_title = 'Wait Until Dark'; + var customer_id = 4; + var date = '2015-09-20'; + + rental.checkIn(movie_title, date, customer_id, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.changed, 1); + + rental.findBy('customer_id', 4, function(err, row) { + assert.equal(err, undefined); + assert.equal(row[0].return_date, '2015-09-20'); + + rental.findBy('customer_id', 9, function(err, row) { + assert.equal(err, undefined); + assert.equal(row[0].return_date, ''); + done(); + }); + }); + }); + }); + }); + + describe('#checkOut', function() { + var validRentalData; + + beforeEach(function(done) { + var data = { + customers: [ + { name: 'Customer0', account_balance: 200 }, + { name: 'Customer1', account_balance: 650 }, + { name: 'Customer3', account_balance: 1000 }, + ], + movies: [ { title: 'Movie1', inventory: 1 } ] + } + resetTables(data, done) + + validRentalData = { checkout_date: '2015-03-16', movie_title: 'Movie1', customer_id: '2' }; + }); + + it('creates a new rental record', function(done) { + rental.checkOut(validRentalData, function(err, res) { + assert.equal(err, undefined); + assert.equal(res.insertedRentalID, 1); + assert.equal(res.changes, 2); + rental.all(function(err, res) { + assert.equal(res.length, 1); + done(); + }); + }); + }); + + it("subtracts 250 from the customer's balance", function(done) { + rental.checkOut(validRentalData, function(err, res) { + new Customer().findBy('id', 2, function(err2, res2) { + assert.equal(err, undefined); + assert.equal(res2.length, 1); + assert.equal(res2[0].account_balance, 400); + done(); + }); + }); + }); + }); +}); diff --git a/utils/schema.js b/utils/schema.js new file mode 100644 index 0000000..947a63f --- /dev/null +++ b/utils/schema.js @@ -0,0 +1,58 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db = new sqlite3.Database('db/' + db_env + '.db'); + +var data = [ + { + name: 'movies', + fields: [ + 'title TEXT NOT NULL UNIQUE', + 'overview TEXT', + 'release_date TEXT', + 'inventory INTEGER NOT NULL DEFAULT 0' + ] + }, + { + name: 'customers', + fields: [ + 'name TEXT NOT NULL', + 'registered_at TEXT', + 'address TEXT', + 'city TEXT', + 'state TEXT', + 'postal_code TEXT', + 'phone TEXT', + 'account_balance INTEGER NOT NULL DEFAULT 0' + ] + }, + { + name: 'rentals', + fields: [ + 'checkout_date TEXT NOT NULL DEFAULT CURRENT_DATE', + 'return_date TEXT', + 'movie_title TEXT NOT NULL', + 'customer_id INTEGER NOT NULL', + 'FOREIGN KEY(movie_title) REFERENCES movies(title)', + 'FOREIGN KEY(customer_id) REFERENCES customers(id)' + ] + } +]; + +db.serialize(function() { + for (var i = 0; i < data.length; i++) { + var table = data[i]; + + db.run("DROP TABLE IF EXISTS " + table.name + ";"); + + var statement = "CREATE TABLE " + table.name + " (id INTEGER PRIMARY KEY"; + for (var j = 0; j < table.fields.length; j++) { + statement += ", " + table.fields[j]; + } + statement += ");" + db.run(statement); + } +}); + +db.close(); diff --git a/utils/seed.js b/utils/seed.js new file mode 100644 index 0000000..88d8cb4 --- /dev/null +++ b/utils/seed.js @@ -0,0 +1,115 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db = new sqlite3.Database('db/' + db_env + '.db'); + +db.serialize(function seedData() { + db.parallelize(function() { + db.serialize(function seedMovies() { + var statement = db.prepare( + 'INSERT INTO movies (title, overview, release_date, inventory) \ + VALUES (?, ?, ?, ?);' + ); + var movies = require('./seed_data/movies.json'); + + for (var i = 0; i < movies.length; i++) { + var movie = movies[i]; + + statement.run( + movie.title, + movie.overview, + movie.release_date, + movie.inventory + ); + } + + statement.finalize(); + }); + + db.serialize(function seedCustomers() { + var statement = db.prepare( + 'INSERT INTO customers (name, registered_at, address, city, state, postal_code, phone, account_balance) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?);' + ); + var customers = require('./seed_data/customers.json'); + + for (var i = 0; i < customers.length; i++) { + var customer = customers[i]; + + var date = new Date(customer.registered_at); + var date = date.getFullYear() + '-' + to2Digits(date.getMonth() + 1) + '-' + to2Digits(date.getDate()); + + statement.run( + customer.name, + date, + customer.address, + customer.city, + customer.state, + customer.postal_code, + customer.phone, + parseInt(customer.account_credit * 100) + ); + } + + statement.finalize(); + }); + }); + + db.serialize(function seedRentals() { + var statement = db.prepare( + 'INSERT INTO rentals (checkout_date, return_date, movie_title, customer_id) \ + VALUES (?, ?, ?, ?);' + ); + + var rentals = require('./seed_data/rentals.json'); + + for (var i = 0; i < rentals.length; i++) { + var rental = rentals[i]; + + statement.run( + rental.checkout_date, + rental.return_date, + rental.movie_title, + rental.customer_id + ); + } + + statement.finalize(); + }); + + // db.serialize(function seedRentals() { + // var numRentals = 10; + // db.all('SELECT * FROM movies LIMIT ' + numRentals + ';', createRentals); + // }); +}); + +// // TODO: come back to this! It would be nice to import the dates. Until then, we'll use the other one. +// function createRentals(err, movies) { +// if (err) { console.log('You suck :P'); return; } + +// var db = new sqlite3.Database('db/' + db_env + '.db'); + +// db.serialize(function() { +// var statement = db.prepare( +// 'INSERT INTO rentals (movie_title, customer_id) \ +// VALUES (?, ?);' +// ); + +// for (var i = 0; i < movies.length; i++) { +// var movie = movies[i]; +// statement.run(movie.title, i); +// } + +// statement.finalize(); + +// db.close(); +// }); +// } + +db.close(); + +function to2Digits(num) { + num = num < 10 ? '0' + num : num; + return num; +} diff --git a/customers.json b/utils/seed_data/customers.json similarity index 100% rename from customers.json rename to utils/seed_data/customers.json diff --git a/movies.json b/utils/seed_data/movies.json similarity index 100% rename from movies.json rename to utils/seed_data/movies.json diff --git a/utils/seed_data/rentals.json b/utils/seed_data/rentals.json new file mode 100644 index 0000000..0432775 --- /dev/null +++ b/utils/seed_data/rentals.json @@ -0,0 +1,62 @@ +[ + { + "checkout_date": "2015-03-16", + "return_date": "2015-03-20", + "movie_title": "North by Northwest", + "customer_id": 2 + }, + { + "checkout_date": "2014-12-16", + "return_date": "", + "movie_title": "The Great Escape", + "customer_id": 15 + }, + { + "checkout_date": "2015-02-16", + "return_date": "2015-02-20", + "movie_title": "E.T. the Extra-Terrestrial", + "customer_id": 20 + }, + { + "checkout_date": "2014-11-16", + "return_date": "2015-01-01", + "movie_title": "Pulp Fiction", + "customer_id": 28 + }, + { + "checkout_date": "2015-09-16", + "return_date": "", + "movie_title": "Wait Until Dark", + "customer_id": 9 + }, + { + "checkout_date": "2015-01-16", + "return_date": "", + "movie_title": "The Bridge on the River Kwai", + "customer_id": 42 + }, + { + "checkout_date": "2015-06-16", + "return_date": "", + "movie_title": "The Treasure of the Sierra Madre", + "customer_id": 66 + }, + { + "checkout_date": "2014-04-16", + "return_date": "2014-04-18", + "movie_title": "Butch Cassidy and the Sundance Kid", + "customer_id": 95 + }, + { + "checkout_date": "2015-09-17", + "return_date": "", + "movie_title": "The Magnificent Seven", + "customer_id": 56 + }, + { + "checkout_date": "2015-05-16", + "return_date": "2015-05-20", + "movie_title": "12 Angry Men", + "customer_id": 104 + } +]