Coder Social home page Coder Social logo

devlucky / kakapo.js Goto Github PK

View Code? Open in Web Editor NEW
530.0 13.0 23.0 5.44 MB

:bird: Next generation mocking framework in Javascript

Home Page: http://devlucky.github.io/kakapo-js

License: MIT License

JavaScript 4.09% TypeScript 95.91%
router database interceptor mocking mocking-framework xmlhttprequest mock mock-server stubbing

kakapo.js's Issues

Response headers support

import {Response, Server} from 'Kakapo';

const server = new Server();

server.get('/users/:id', (request, db) => {
   const body = db.find('user', request.params.id);
   const statusCode = body ? 200 : 401;
   const headers = {'version': 'v1', 'x-custom-header': 'foo'};

   return new Response(statusCode, body, headers);
});

Replace some ES6 functions with lodash

Things like Object.keys() and Object.assign() are not polyfill'ed with babel. We should consider changing those two to _.keys() and _.assign() in favor of browsers without full support of ES6. What do you think @zzarcon?

Route handling

The Router must use https://github.com/pillarjs/path-to-regexp to handle requests

Draft...

import {router} from 'kakapo';

router.get('/users', usersHandler);
router.post('/users/:user_id', createUser);

function usersHandler(request, db) {
  return {
    users: db.all('user');
  };
};

function createUser(request, db) {
  db.create('user', request.params.id);
};

Scenario support

Basically the ability to easy enable/disable components in the server

import {Router, DB, Server} from 'Kakapo';

const db1 = new DB();
db1.register('user', userFactory);
db1.register('user', commentFactory);
db1.create('user', 10);
db1.create('comment', 50);

const db2 = new DB();
db2.register('user', userFactory);
db2.create('user', 10000);

const router1 = new Router();
router1.get('v1/users', (request, db) => {db.all('user')});

const router2 = new Router({host: 'www.devlucky.com'});
router1.get('v2/user/all', (request, db) => {db.all('user')});

const server = new Server();

//Important part: 
server.use(db1);
server.use(router2);

Database relationships

Relationship proposal

import {DB} from '../src/kakapo';

const db = new DB();

db.register('user', userFactory);
db.register('post', postFactory);
db.register('like', likeFactory);

function userFactory() {
  return {
    firstName: faker.name.firstName,
    lastName: 'Zarco',
    avatar: faker.internet.avatar,
    age: 24
  };
}

function postFactory(faker) {
   return {
     text: 'Foo',
     date: faker.date
     likes: db.hasMany('like'),
     author: db.belongsTo('user')
   };
}

function likeFactory(faker) {
  return {
    date: faker.date,
    author: db.belongsTo('user', 1)
  }
}

Create Examples

We should provide working examples as a showcase of the project

Serializers

Offer default Serializers that will be pluggable to the request handler:

  • JSONSerializer
  • RESTSerializer
  • JSONApiSerializer
import {DB, JSONApiSerializer} from 'Kakapo';

const db = new DB();
db.register('user', (faker) => {
  return JSONApiSerializer({
    firstName: faker.name.firstName,
    lastName: 'Zarco',
    avatar: faker.internet.avatar,
    age: 24
  });
});

This will return a payload following the JSONApi standards:

{
  "data": [{
    "id": 1,
    "type": "user",
    "attributes": {
      "firstName": "Hector",
      "lastName": "Zarco",
      "avatar": "http://url",
      "age": 24
    }
  }]
}

Also the user must be able to define a custom serializers:

const ApiV1Serializer = (payload) => {
  return {
    version: 'v1',
    data: payload,
    status: {
      code: 200,
      text: 'Success'
    }
  };
};

Separate stores from database

I was thinking about making it impossible for user to use db.store, db.factories, etc. I came up with this solution, @zzarcon please review :)

// stores.js

const storeFactory = () => {
  const store = {};

  return {
    get(collectionName) {
      return store[collectionName];
    }

    set(collectionName, value) {
      store[collectionName] = value;
    }
  }
};

export const createStores = () => ({
  factories: storeFactory(),
  records: storeFactory(),
  uuids: storeFactory()
});
// recordFactory.js
const updateRecord = (record, collectionName, { records }) => {
  const originalRecord = records.find(collectionName, {id: record.id});
  return Object.assign(originalRecord, record);
};

const assignId = (collectionName, { uuids }) => {
    const id = uuids[collectionName] || 0;
    uuids[collectionName] = id + 1;

    return id;
};

export const recordFactory = (record, collectionName, stores) => {
  record.save = () => updateRecord(record, collectionName, stores);
  record.id = assignId(collectionName, stores);

  return record;
};
// database.js
import { createStores } from './createStores';
import { recordFactory } from './recordFactory';

const stores = createStores();

const pushToRecords = (records, collectionName, stores) => stores.set(collectionName,
  records.map(record => recordFactory(record, collectionName, stores)));

export class Database {
// ...
all(collectionName) {
  return stores.records.get(collectionName);
}
// ...
};

Chrome extension

Chrome extension that will all the requested urls, the handled ones, his headers, body, etc...

Disconnect feature

import {Server, Router} from 'kakapo';

const server = new Server();
const router = new Router();

router.get('/foo', () => {foo: 'bar'});
server.use(router);

fetch('/foo') //request intercepted

server.remove(router);

fetch('/foo'); //request skipped

Same thing with Databases;

Implement RecordFactory

RecordFactory should attach certain methods / properties to record, like: save() before pushing it into proper store. @zzarcon?

let user = db.findById('user', 0); 

user.name = 'paco';

user.save(); // updates this record in db

Response Codes

Support response code somehow...
Also supporting give support for common codes somehow? like SUCCESS, NOT_FOUND, UNAUTHORIZED

Database fake data handling

The Database must use https://github.com/Marak/faker.js in order to populate the factory records

Draft...

import {DB} from '../src/kakapo';

const db = new DB();
db.register('user', userFactory);

function userFactory(faker) {
  return {
    firstName: faker.name.firstName,
    lastName: 'Zarco',
    avatar: faker.internet.avatar,
    age: 24
  };
}

Whenever the DB generate the records it will check the value of the properties, if the value is a function it will execute. That way will can easily define random data

Intercept requests

The Server class should be able to listen for request on the following implementations:

  • Fetch Api
    • Method support: GET, POST, PUT, DELETE
    • Headers support
  • XMLHttpRequest

Maybe we should consider using a different module for it, something like Interceptor? and just isolate there the functionality and integrate in on the Server...

Router options

Router should admit the following options

  • host
  • requestDelay
  • defaultResponseHeaders - Not sure about this one...

Create workflow for auto-generating documentation

  • Document all sections using jsdocs
    • database
    • helpers
    • interceptors
    • response
    • router
    • serializers
    • server
  • Integrate code with documentation.js to read it and produce .md files
  • Create Makefile script to compile comments to doc file like above
  • Create githooks to always update documentation on push

Host proposal

Different proposals for host handling

1

gtihubServer = new Router({host: 'api.github.com'});gtihubServer.get('/emojis', handler)
gtihubServer.get('/users', handler)
gtihubServer.get('/user/:id', handler)

runtasticServer = new Router({host: 'runtastic.com'});runtasticServer.get('/emojis', handler)
runtasticServer.get('/users', handler)
runtasticServer.get('/user/:id', handler)

2

server = new Router();server.get('/emojis', handler, {host: 'api.github.com'})
server.get('/users', handler, {host: 'api.github.com'})
server.get('/user/:id', handler, {host: 'api.github.com'})

server.get('/users', handler, {host: 'runtastic.com'})
server.post('/users', handler, {host: 'runtastic.com'})

3

server = new Router();server.namespace('api.github.com')
server.get('/emojis', handler)
server.get('/users', handler)
server.get('/user/:id', handler)

server.namespace('runtastic.com')
server.get('/users', handler)
server.post('/users', handler)

I really like the 3 since allow us to easily define handlers for different host without being that verbose.

PD: Please forget about the router/server concept, let's asume is the same for now

Explicit autoincrement

What about just being explicit on this and avoid possible future errors?

import {DB} from 'Kakapo';

const db = new DB();

db.register('user', userFactory);

const userFactory = (faker, constrains) => {
  return {
    id: constrains.autoincrement(),
    firstName: 'hector',
    lastName: 'zarco'
  };
}

@joanromano @MP0w @oskarcieslik

Hooks

Basically subscribe to router events, proposal:

import {Router} from 'Kakapo';

const router = new Router();

router.on('handledRequest', (verb, path, request) => {

});

router.on('unhandledRequest', (verb, path, request) => {

});

router.on('erroredRequest', (verb, path, request, error) => {

});

Server duties

The Server component is in charge of connect all the other Kakapo pieces. A real example could be something like:

import {Router, DB, Server} from 'Kakapo';

const db = new DB();
db.register('user', (faker) => {
  return {
    firstName: faker.name.firstName,
    lastName: 'Zarco',
    avatar: faker.internet.avatar,
    age: 24
  };
});

const router = new Router({host: 'custom'});
router.get('/users/', (request, db) => {
  return db.all('user');
});

router.get('/users/:user_id', (request, db) => {
  return db.find('user', request.params.id);
});

router.post('/users/', (request, db) => {
  const createdUser = db.create('user', request.body.user.id);

  return createdUser;
});


const server = new Server({requestDelay: 500});

server.use(db);
server.use(router);

Support factories with nested properties

Factories like this must work:

const userFactory = faker => ({
  name: 'Hector'
  address: {
    zipCode: faker.address.zipCode,
    city: faker.address.city,
    streetName: faker.address.streetName,
  }
});

Create App examples

Create real life working examples inside examples folder

  • TODO App - #79
  • Github user explorer

Support other environments in interceptors

Right our interceptors only replace window.fetch / window.XMLHttpRequest. I think we need to check if framework is used inside of browser, service worker, react native, etc and then assign fake service to window / global / self

Request headers

Right now we support Response headers, we also should provide support for request headers handling.

import {Server} from 'Kakapo';

const server = new Server();
server.post('/users', (request) => {
  request.headers === {'X-Custom-Value': 'VALUE'};
});

var headers = new Headers({'X-Custom-Value': 'VALUE'});
var options = {
  method: 'POST',
  headers: headers
};
var request = new Request('/users');

fetch(request, options);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.