devlucky / kakapo.js Goto Github PK
View Code? Open in Web Editor NEW:bird: Next generation mocking framework in Javascript
Home Page: http://devlucky.github.io/kakapo-js
License: MIT License
:bird: Next generation mocking framework in Javascript
Home Page: http://devlucky.github.io/kakapo-js
License: MIT License
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);
});
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?
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);
};
I think that we should change names:
filter
=> find
find
=> findOne
This is pre-issue for #52
We must ensure that the response which is returned by the fetch interceptor
is an instance of Response
fetch('/users', (response) => {
console.log(response instanceof Response === true);
});
I think we need to deploy the apps somewhere in order to demo them
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);
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)
}
}
This is a must for functions like record.save()
to be useful and framework more bug-proof.
We should provide working examples as a showcase of the project
Offer default Serializers that will be pluggable to the request handler:
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'
}
};
};
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 that will all the requested urls, the handled ones, his headers, body, etc...
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
;
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
Support response code somehow...
Also supporting give support for common codes somehow? like SUCCESS
, NOT_FOUND
, UNAUTHORIZED
Cause it's awesome ;)
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
const user = db.find('user', {city: {name: 'valencia'}})
I would like to have those separate
This is the expected behaviour:
const router = new Router();
router.get('/users/:user_id', (request) => {
request.params.user_id === 1;
});
fetch('/users/1');
Write down all code smells / things that annoy us and let's refactor a bit ;) This is for Beta-1 milestone but it should be completed LAST.
The Server
class should be able to listen for request on the following implementations:
GET
, POST
, PUT
, DELETE
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 should admit the following options
host
requestDelay
defaultResponseHeaders
- Not sure about this one...jsdocs
documentation.js
to read it and produce .md
filesdoc
file like aboveWill be cool to persist the Database in:
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
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
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) => {
});
There's some similar functionality in both fakeXMLHttpRequest & fakeFetch, like: reset, saving native function and saving routes. I think we can abstract those three behaviours into some kind of wrapper. What do you think @zzarcon? :)
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);
Investigate if we can easily extend all the existing functionalities of XMLHttpRequest
and just override some methods
After #51 we need to create some kind of dope template like ramda or lo-dash is using 💪
Instead use find / filter / all
Factories like this must work:
const userFactory = faker => ({
name: 'Hector'
address: {
zipCode: faker.address.zipCode,
city: faker.address.city,
streetName: faker.address.streetName,
}
});
Create real life working examples inside examples folder
We must provide a way to not include all Kakapo code in the production build...
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
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);
Ava / Mocha + chai. @zzarcon ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.