Giter Site home page Giter Site 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 People

Contributors

atl-jluong avatar jquintana-atlassian avatar rpunkfu avatar savcni01 avatar soswow avatar zzarcon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kakapo.js's Issues

Response Codes

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

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

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);

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,
  }
});

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

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?

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...

Create App examples

Create real life working examples inside examples folder

  • TODO App - #79
  • Github user explorer

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)
  }
}

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

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) => {

});

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'
    }
  };
};

Create Examples

We should provide working examples as a showcase of the project

Chrome extension

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

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

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);

Router options

Router should admit the following options

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

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

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

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);
}
// ...
};

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;

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);

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);
};

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);
});

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.