<file_sep># supergoose
Example App using SuperTest and Mockgoose for API &amp; Mongoose model testing

[![Build Status](https://travis-ci.com/JB-Tellez/supergoose.svg?branch=master)](https://travis-ci.com/JB-Tellez/supergoose)
<file_sep>import mongoose, {Schema} from 'mongoose';

const schema = new Schema({
  name : String,
  instrument: { type: String, default: 'singer'},
  band: { type: Schema.Types.ObjectId, ref : 'Band'},
});

export default mongoose.model('Musician', schema);<file_sep>import mongoose, {Schema} from 'mongoose';

const schema = new Schema({
  name : String,
});

export default mongoose.model('Band', schema);<file_sep>import mongoose from 'mongoose';
import {
  Mockgoose,
} from 'mockgoose-fix';

// Give a longer timeout for when Travis runs slow
jest.setTimeout(60000);

const mockgoose = new Mockgoose(mongoose);

export default {
  prepare: () => {
    return mockgoose.prepareStorage().then(() => {
      return mongoose.connect('mongodb://localhost/supergoose').catch(err => {
        console.log('already connected, no sweat', err);
      });
    }).catch(console.error);
  },
  
};

afterEach((done) => {
  // console.log('after each');
  mockgoose.helper.reset().then(done);
});<file_sep>import { Router } from 'express';
import Musician from './model';
import auth from '../auth/middleware';

const router = new Router();

router.get('/', (req, res, next) => {
  Musician
    .find()
    .then(bands => res.send(bands))
    .catch(err => next(err));
});

router.post('/', (req, res, next) => {
  Musician
    .create(req.body)
    .then(band => res.send(band))
    .catch(next);
});

router.put('/:id', (req, res, next) => {
  Musician.findByIdAndUpdate(req.params.id, req.body)
    .then(band => res.send(band))
    .catch(next);
});

router.delete('/:id', (req, res, next) => {
  Musician.findByIdAndRemove(req.params.id)
    .then(results => res.send(results))
    .catch(next);
});

router.get('/:id', (req, res, next) => {
  Musician
    .findById(req.params.id)
    .then(band => res.send(band))
    .catch(next);
});

router.get('/protected', auth, (req, res) => {
  res.send('the keys to the Musician kingdom');
});

export default router;
<file_sep>import supertest from 'supertest';
import superagent from 'superagent';

const {
  app,
} = require('../../src/app.js');

const mockRequest = supertest(app);
const API_STUB = '/api/v1';

describe('app module', () => {

  it('supertest should exist', () => {
    expect(supertest);
  });

  it('app should exist', () => {
    expect(app);
  });

  it('should sign up and return token', async () => {

    const token = await mockRequest
      .post('/signup')
      .send({
        username: 'foo',
        password: 'bar',
      })
      .then(response => response.text)
      .catch(fail);

    expect(token).toBeDefined();
  });

  it('should sign in', async () => {

    await mockRequest
      .post('/signup')
      .send({
        username: 'foo',
        password: 'bar',
      });

    await mockRequest
      .get('/signin')
      .auth('foo', 'bar')
      .then(response => {
        expect(response.text).toBe('Hi');
      });
  });

  it('should fail sign in with bad password', async () => {

    await mockRequest
      .post('/signup')
      .send({
        username: 'foo',
        password: 'bar',
      });

    await mockRequest
      .get('/signin')
      .auth('foo', 'badpassword')
      .then(response => {
        expect(response.status).toBe(401);
      });
  });

  it('should fail sign in with unknown name', async () => {

    await mockRequest
      .post('/signup')
      .send({
        username: 'foo',
        password: 'bar',
      });

    const response = await mockRequest
      .get('/signin')
      .auth('nobody', 'bar');

    expect(response.status).toBe(401);
  });

  it('should give access to protected route with token', async () => {

    const token = await mockRequest
      .post('/signup')
      .send({
        username: 'foo',
        password: 'bar',
      })
      .then(response => response.text);

    const response = await mockRequest
      .get('/api/v1/bands/protected')
      .set('Authorization', 'Bearer ' + token)
      .expect(200)
      .catch(fail);

    expect(response.text).toBe('the keys to the band kingdom');
  });

  it('should protect route when no token', async () => {

    const response = await mockRequest.get('/api/v1/bands/protected');

    expect(response.status).toBe(401);

  });

  it('should protect route when bad token', async () => {

    const response = await mockRequest
      .get('/api/v1/bands/protected')
      .set('Authorization', 'Bearer ' + 'badtoken');

    expect(response.status).toBe(401);

  });

  it('should get [] for initial request to GET bands', async () => {

    const response = await mockRequest.get(API_STUB + '/bands');
    
    expect(JSON.parse(response.text)).toEqual([]);
  });

  it('should get populated list after creating', async () => {

    let response = await mockRequest.post(API_STUB + '/bands')
      .send({
        name: 'The Who',
      });


    expect(response.body.name).toBe('The Who');

    response = await mockRequest.get(API_STUB + '/bands');

    expect(response.body.length).toBe(1);
  });

  it('should get error 404 if unfound id', async () => {

    const response = await mockRequest.get(API_STUB + '/bands/unknown_id');

    expect(response.status).toBe(404);

  });

  it('should update resource', async () => {

    let response = await mockRequest.post(API_STUB + '/bands')
      .send({
        name: 'The Who',
      });

    const band = response.body;

    response = await mockRequest
      .put(API_STUB + '/bands/' + band._id)
      .send({name : 'The Whom'})
      .catch(fail);
    
    expect(response.status).toBe(200);
    expect(response.body.name).toBe('The Whom');

  });
});<file_sep>import mongoose, {Schema} from 'mongoose';

const schema = new Schema({
  name : String,
  band: { type: Schema.Types.ObjectId, ref : 'Band'},
});

export default mongoose.model('Roadie', schema);<file_sep>import Roadie from '../../../src/roadies/model';

describe('Roadie model', () => {

  it('should be defined', () => {
    expect(Roadie).toBeDefined();
  });
});<file_sep>import Musician from '../../../src/musicians/model';
import Band from '../../../src/bands/model';
import Roadie from '../../../src/roadies/model';

describe('band + roadie', () => {

  it('should have a Roadie as Id', async () => {

    const band = await Band.create({name:'<NAME>'});

    const roadie = await Roadie.create({name: '<NAME>', band: band._id});
    
    expect(roadie.band._id).toBe(band._id);
     
  });

  it('should have Roadie as populated object', async () => {
    
    const band = await Band.create({name:'<NAME>'});

    const roadie = await Roadie.create({name: '<NAME>', band: band._id});

    const foundRoadie = await Roadie.findById(roadie._id).populate('band').exec();

    expect(foundRoadie.band.name).toBe(band.name);

  });
});

describe('band + musician', () => {

  it('should do musician + band - async style', async () => {

    const fooFighters = await Band.create({name:'<NAME>'});

    const dave = await Musician.create({name: '<NAME>', band: fooFighters._id});

    const popDave = await Musician.findById(dave._id).populate('band').exec();

    expect(popDave.band.name).toBe('Foo Fighters');
  });

  it('should find the musicians in a band', async () => {

    const beeGees = await Band.create({name: '<NAME>'});

    await Musician.create({name : 'Barry', band: beeGees._id});
    await Musician.create({name : 'Robin', band: beeGees._id});
    await Musician.create({name : 'Maurice', band: beeGees._id});

    const stooges = await Band.create({name: 'The Stooges'});
    await Musician.create({name: '<NAME>', band: stooges._id});

    const musicians = await Musician.find({band:beeGees._id}).populate('band').exec();
    
    expect(musicians.length).toEqual(3);
    expect(musicians[0].band.name).toBe('Bee Gees');
    
  });
});

describe('Band model', () => {

  /* NOTE: you probably won't do tests like these
  * since they're really testing Mongoose internals
  * where we want to test our code.
  * But including them for instructional purposes */

  it('should be defined', () => {
    expect(Band).toBeDefined();
  });

  it('should create', async () => {

    const band = await Band.create({name: 'Spinal Tap'});

    expect(band.name).toBe('Spinal Tap');

  });

  it('should get one', async () => {

    const rawBand = {
      name: 'Spinal Tap',
    };

    const band = await Band.create(rawBand);

    const foundBand = await Band.findById(band._id);

    expect(foundBand.name).toEqual(band.name);
  });

  it('should track creations when getting all - async/await style', async () => {

    let bands = await Band.find();
    
    expect(bands.length).toBe(0);

    await Band.create({
      name: 'Fugazi',
    });

    bands = await Band.find();
    
    expect(bands.length).toBe(1);

  });

  it('should track creations when getting all - promise style', () => {

    return Band
      .find()
      .then(bands => expect(bands.length).toBe(0))
      .then(() => Band.create({ name: 'Fugazi' }))
      .then(() => Band.find())
      .then(bands => expect(bands.length).toBe(1));

  });
});
<file_sep>'use strict';
import mongoose from 'mongoose';
import { JsonWebTokenError }  from 'jsonwebtoken';

// Custom Error Handler because we always want to return a JSON response
export default  (err,req,res,next) => {

  if(err instanceof mongoose.Error) {
    
    res.statusCode = 404;

    res.send('Database Error');
    
  } else if (err instanceof JsonWebTokenError) {

    res.statusCode = 401;

    res.send(err);

  } else {

    res.statusCode = err.status || 500;
    res.statusMessage = err.statusMessage || 'Server Error';
    res.message = err.message;
    
    res.send(err);
  }
};<file_sep>
import helper from './test-helper';

helper.prepare();<file_sep>import express from 'express';
import bandsRouter from './bands/router';
import musiciansRouter from './musicians/router';
import roadiesRouter from './roadies/router';
import errorHandler from './middleware/error';
import notFound from './middleware/404';
import authRouter from './auth/router';

const app = express();

app.use(express.json());

app.use(authRouter);

app.use('/api/v1/bands', bandsRouter);
app.use('/api/v1/musicians', musiciansRouter);
app.use('/api/v1/roadies', roadiesRouter);

app.use(notFound);

app.use(errorHandler);

module.exports = {

  start: port => {
    app.listen(port, () => console.log('Listening on port', port));
  },
  stop: () => app.close(),
  app,
};