Refactoring mock response in ember cli mirage 0.2.x - javascript

I'm using ember cli mirage to write some acceptance tests for my Ember app. I succeeded to mock server response for login but I'm not happy how I did it. Ember cli mirage have shorthands for route handlers and I would like to use them but everything I try throws me an error(except this solution). Can someone help me to refactor this response?
this.post('/login', ({ users, resources })=> {
let user = users.first();
if(!Ember.isEmpty(resources.first())){
return {
data: {
type: 'user',
id: user.id,
attributes: user,
relationships: {
resources: {
data: [
{ id: resources.first().id, type: 'resource' }
]
}
}
},
};
} else {
return {
data: {
type: 'user',
id: user.id,
attributes: user
}
};
}
});
I have both user and resource model and factory defined, with relationships between them in user and resource model(it's many to many relationship). Here's how I create user in tests
test('User can login', function(assert){
let resources = server.createList('resource', 2),
user = server.create('user', {resources: resources});
loginUser(user.email);
andThen(()=>{
assert.ok(find('a:contains("Logout")'));
assert.equal('resource.content', currentPath());
});
});

If it's many-to-many, you should explicitly create a join record, as direct m2m relationship support does not yet exist.
// mirage/models/user.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
userResources: hasMany()
});
// mirage/models/resource.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
userResources: hasMany()
});
// mirage/models/user-resource.js
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
user: belongsTo(),
resource: belongsTo()
});
test('User can login', function(assert){
let user = server.create('user');
let resources = server.createList('resource', 2),
// create the join records
resources.forEach(resource => {
server.create('user-resource', { user, resource });
});
loginUser(user.email);
andThen(() => {
assert.ok(find('a:contains("Logout")'));
assert.equal('resource.content', currentPath());
});
});
If you need to mock an endpoint that exposes the m2m directly it will take a bit more work. But in general I find that if your Ember app exposes CRUD operations on the relationship, it's good to expose the join record, too. Makes things simpler.
That being said, Mirage will eventually support m2m relationships.

Related

Websockets in Vue/Vuex (how to receive emissions from server)

So until now I just socket.io-client to do communication to a WebSocket in my Vue component.
Now I am adding Vuex to the project and declared a Websocket like this
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
}));
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
1) Should i have stuff like emitting some messages in the component themselves now or in the store?
2) Before I introduced the changes I could do something like this:
socket.on('connect', function () {
console.error('connected to webSocket');
socket.emit('my event', { data: 'I\'m connected!' });
});
socket.on('my response', function(data){
console.log('got response');
console.log(data.data);
});
When sending the "my event", the flask server would respond with "my response". Now I am trying the same thing from a component after the changes like this.
this.$socket.emit('my_event', { data: 'I\'m connected!' });
console.error('send to websocket ');
this.$options.sockets.my_event = (data) => {
console.error('received answer ');
console.error(data);
};
The my_event reaches my flask server however I don't get the response receiving to work. What am I doing wrong?
Also because I was asking about whether I should put this in the component or the store, I found stuff like this for the store:
SOCKET_MESSAGECHANNEL(state, message) {
state.socketMessage = message
}
The explanation was "So, for example, if your channel is called messageChannel, the corresponding Vuex mutation would be SOCKET_MESSAGECHANNEL" and it is from this site https://alligator.io/vuejs/vue-socketio/.
I think I don't really get what a channel is at this point. Is the my_response I emit from the flask server also a channel?
Thanks for your help in advance!
EDIT: So now I am trying to listen and emit to a websocket from my store. For this I tried the following: In main.js I have this part:
Vue.use(new VueSocketIO({
debug: true,
connection: SocketIO('http://192.168.0.38:5000'),
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_',
},
}));
Then in my store.js I have the following:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
title: 'title from vuex store',
isConnected: false,
},
mutations: {
increment(state) {
state.count += 1;
},
emitSth(state) {
this.sockets.emit('my_event', { data: 'I\'m connected!' });
console.log(state.count);
},
SOCKET_my_response(state) {
state.isConnected = true;
alert(state.isConnected);
},
SOCKET_connect(state) {
state.isConnected = true;
alert(state.isConnected);
},
},
});
And in my component I have this script:
export default {
name: 'ControlCenter',
data() {
return {
devices: [{ ip: 'yet unknown' }], // placeholder so line 12 does not throw error before actual device info fetched
thisDeviceIndex: 0,
currentLayoutIndex: 0,
layouts: [],
};
},
computed: mapState([
'title',
'count',
]),
components: {
DNDAssign,
FirstPage,
},
methods: {
// mapMutation helper let's us use mutation from store via this instead of this.$store
...mapMutations([
'increment',
'emitSth',
]),
incrementMutation() {
this.increment();
},
emitEvent() {
this.emitSth();
},
// some other stuff here
},
created() {
// inital fetching of layouts
console.log('fetching layouts from backend');
this.getAllLayouts();
console.log(this.$socket);
},
};
I also have a button for the triggering of the emit which is
<b-button
type="button"
variant="success"
v-on:click="emitEvent()"
>
emit event
</b-button>
The connected in the store gets triggered, however I get the following errors for the emitting:
"TypeError: Cannot read property 'emit' of undefined"
"Cannot read property 'emit' of undefined"
Also I am not sure about the naming in the mutations. If I have this mutationPrefix, shouldn't it be enough to just use connect instead of SOCKET_connect?
First of all, if you are using Vue-Socket.io version 3.0.5>, uninstall it and install version 3.0.5
npm uninstall vue-socket.io
npm install vue-socket.io#3.0.5
then lock the version in packege.json: "vue-socket.io": "3.0.5", latest update seems to breaks the library, read more here
Now to receive events from socket.io server, you can do:
this.sockets.subscribe("my response", (data) => {
console.log(data);
});
or if want put listener on component level, you need to add sockets object on the component export, for example:
export default {
...
sockets: {
"my response": function (data) {
console.log(data);
}
}
...
}
Since you are not using Vuex Integration on the VueSocketIO, you dont need to put additional function in store mutation. If you want to use Vuex integration on VueSocketIO, you need to add vuex object when declaring the VueSocketIO class.
Here's the basic example for main.js
// Set Vue to use Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
state: {
someData: null
},
getters: {},
actions: {
"SOCKET_my response"(context, data) {
// Received `my response`, do something with the data, in this case we are going to call mutation "setData"
context.commit("setData", data);
}
}
mutations: {
["setData"](state, data) {
state.someData = data; // Set it to state
}
}
});
// Set Vue to use VueSocketIO with Vuex integration
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
vuex: {
store,
actionPrefix: "SOCKET_"
}
}));
new Vue({
router,
store
render: h => h(App)
}).$mount("#app");
If you need example on Vuex Integration, you can check my example app that uses Vue and Vue-Socket.io with Vuex integration.te

How to update clients when database is edited outside of feathersjs app

I have two server-side applications editing the same database.
One server is a feathersjs app and another is a simple nodejs app. A frontend app connects to the feathersjs app via feathersjs client.
How, when the nodejs app edits the database, can I update clients connected to the feathersjs app? As currently any changes made outside the featherjs app aren't reflected on the feathersjs clients.
Can I trigger the patched event somehow and force the clients to pull down the updated data?
if you are using mongodb with WiredTiger storageEngine you can use the collection.watch() function and add a monitor in your feathers app something like this
//src/services/monitor.js
module.exports = function(app){
//get mongo client
const mongoClient = app.get('mongoClient');
//connect db
mongoClient.then(db => {
//collection to watch
const collection = db.collection('your_collection_name')
//watch collection
const changeStream = collection.watch({ fullDocument: 'updateLookup' });
//on some data changed
changeStream.on('change', data => {
console.log ( 'something is changed in your collection' , data )
//your service.emit
});
})
}
Then I added this simple monitor in the /src/services/index.js (maybe not the right way but it works)
//src/services/index.js
...
const monitor = require('./monitor.js');
module.exports = function (app) {
...
app.configure(monitor);
...
};
Data returned on every change on the collection
{ _id:
{ _data:
'825C7F03230000001C29295A100490DEF2C65812410FABF0DE46F9C89D7246645F696400645C1AC097B189CBD5D4A24D330004' },
operationType: 'replace',
clusterTime:
Timestamp { _bsontype: 'Timestamp', low_: 28, high_: 1551827747 },
fullDocument:
{ _id: 5c1ac097b189cbd5d4a24d33,
id: '12',
language: 'it-IT',
category: 'some data',
slug: '',
description: 'some data',
src:'',
color: 'card',
status: true,
home: true,
order_int: '3',
visual: 'card' },
ns: { db: 'mydb', coll: 'mycollection' },
documentKey: { _id: 5c1ac097b189cbd5d4a24d33 } }
More info here https://docs.mongodb.com/manual/reference/method/db.collection.watch/
As you pointed out, only changes made through the Feathers API will be reflected but on the server you can always emit the event you need via service.emit:
dbConnection.on('someDatabaseUpdate', data => {
app.service('messages').emit('patched', data);
app.service('messages').emit('updated', data);
});
Things to note here (also discussed in this issue):
There will be no user or any other information about the method call
data will not be run through any service hooks

Sequelize get request data in hooks?

I'm trying to store some log data for my models on create, update, delete calls. I want to store some data from the request along with some user data also in the request (using express.js).
In the hooks I have some modules for logging.
hooks: {
afterCreate: function (order, options, done) {
// How to get user data stored in express request.
return app.log.set('event', [{message: 'created', data: order, userId: 1}, done]);
}
}
...
The module just makes a record in a table. However it's the userId part I'm having trouble with. I'm using the passport module and it's stored in the request, so how can I get a user object (or any external object for that matter) into the model hooks?
I would like to avoid doing it in a controller or anywhere else as there could be some scripts or other commands that may also enter data.
I also encountered similar problems, which I myself resolved as follows:
First: I declared a Global (universal) hook:
module.exports = sequelize.addHook('beforeCreate',
function(model, options, done) {//hook 2
//handle what you want
//return app.log.set('event', [{message: 'created', data: order, userId: 1}, done]);
});
Then, Before calling model, use call hooks (beforeCreate, beforeBulkUpdate,...) and assigned param request
module.exports = {
CreateUser: function(req, res) {
User.beforeCreate(function(model, options, done) {//hook1
model.request = req;
});
User.create({
id: 1,
username: 'thanh9999',
password: '31231233123'
//ex.....
})
.then(function(success) {
//response success
}, function(err) {
//response error
});
}
};
order hooks called: hook declaration in model → hook 1 → hook 2`.
In addition, you also have to declare hooks for each model.
See more information here.

Structure role-management in meteor-app with alanning:roles

I need some advice for building a correct role schema and management in my meteor-app.
Structure
Im using alanning:roles#1.2.13 for adding role management functionallity to the app.
There are four different user-types: Admin, Editor, Expert and User.
Furthermore there are several modules with different content, i.e. Cars, Maths and Images. Every module is organized in an own meteor-package.
In every module there are several categories, which can be added dynamically by editors.
Categories in modules
Module is structured like this:
elementSchema = new SimpleSchema({
element: {type: String, optional: true}
});
Cars.attachSchema(new SimpleSchema({
title: { type: String },
content: { type: String },
category: { type: [elementSchema], optional: true },
});
As you can see, all available categories are inside of the Collection of the module.
Rights
Admin: Complete rights
Editor: Can edit elements in selected moduls (i.e. editor_1 can edit elements in Cars and Images but not for Maths)
Expert: Can get rights to a complete module or just to some categories of a module (i.e.) expert_1 can edit Images, but only the elements in category "Honda" and "Mercedes" in Cars; no editing to Maths)
User: No editing
This is how I do the authentification technically:
router.js
var filters = {
authenticate: function () {
var user;
if (Meteor.loggingIn()) {
this.layout('login');
this.render('loading');
} else {
user = Meteor.user();
if (!user) {
this.layout('login');
this.render('signin');
return;
}
this.layout('Standard');
this.next();
}
}
}
Router.route('/car/:_id', {
name: 'car',
before: filters.authenticate,
data: function () {
return {
cars: Cars.findOne({ _id: this.params._id })
};
}
});
template
<template name="car">
{{#if isInRole 'cars'}}
Some form for editing
{{else}}
<h1>Restricted area</h1>
{{/if}}
</template>
I put this router.js to every package. Only change is the data function which uses the Collection of each package (Cars, Maths, Images).
Update: As 'Eliezer Steinbock' commented it is necessary to restrict acces to the mongoDB itself. But until now I only did that on the routes.
permissions.js
Cars.allow({
insert: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
},
update: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
}
});
My problems
1) My first problem is how to use roles and groups. What would be the best way for using groups? And the second problem is, that there are no fixed categories in the modules. Right now I have no idea for a useful role/group schema.
2) How do I check for the roles? As there are different roles which can get access: admin, editor and expert. Also I got the problem with these experts who just get access to defined categories of this module.
3) Wouldn't it be better to make the permission.js more general. I mean, is it possible to make a dynamic function, so I don't have to put everywhere the same code? How do I implement the roles in the permission.js in a useful way?
if the logic for the permissions is the same you could just define it once in permissions.js
App = App || {}; // We are using Namespaces, so you don't have to.. but it's good
App.Permissions = {
insert: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
},
update: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
}
}
And then you can use it for your Collections:
Cars.allow(App.Permissions); // Or
Cars.allow(App.Permissions.getPermissionsForGroup('cars'))
Define roles somewhere..
Roles
// Give user the role "editor" in "cars" group
Roles.addUsersToRoles(someUserId, ['editor'], 'cars');
Roles.addUsersToRoles(someOtherId, ['admin'], 'cars');
Which you can prepare in permissions.js like this:
Permissions
App = App || {};
App.Permissions = {
insert: function(userId) {...},
update: function(userId) {...},
getPermissionsForGroup: function(group) {
return {
insert: function(userId, doc) {
// Only admin can insert
return Roles.userIsInRole(userId, "admin", group);
},
update: function(userId, doc, fields, modifier) {
// Editor & Admin can edit
return Roles.userIsInRole(userId, ["editor","admin"], group);
},
remove: function(userId, doc) {
// Only admin can remove
return Roles.userIsInRole(userId, "admin", group);
}
}
}
In this example admins can insert and update.. and editors can only update, but insert.
Regarding the documentation of alanning:roles you define and use roles like this:
// Super Admin definition..
Roles.addUsersToRoles(superAdminId, ['admin'], Roles.GLOBAL_GROUP);
Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com')
Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com')
Roles.userIsInRole(joesUserId, 'manage-team', 'manchester-united.com') // => true
Roles.userIsInRole(joesUserId, 'manage-team', 'real-madrid.com') // => false
Yeah, make sure, that the permission logic will be included before your Collection definition.. obviously :)

Meteor Routing, Pub/Sub

I'm trying to make a publishment statement to publish
ONLY the author(OP)'s profile avatar. I am thinking of grabbing the _id of the page. And from that page, I will grab the userId which is the author's _id and try to show the profile.
However, I have been very unsuccessful, and currently, I am using the following. Publishing EVERY user's profile avatar.
Publications.js
//Need to filter this to show only OP.
Meteor.publish("userPostAvatar", function() {
return Meteor.users.find( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
return Posts.findOne({_id:this.params._id});
}
});
You can do a simple join in the userPostAvatar publish function like this:
Meteor.publish('userPostAvatar', function(postId) {
check(postId, String);
var post = Posts.findOne(postId);
return Meteor.users.find(post.authorId, {fields: {profile: 1}});
});
This assumes posts have an authorId field - adjust as needed for your use case. Note three important things:
You will need to subscribe with this.params._id just as you did for singlePost.
The join is non-reactive. If the author changes, the avatar will not be republished. Given the general nature of posts I assume this isn't a problem.
I didn't publish the nested field profile.avatar on purpose because doing so can cause weird behavior on the client. See this question for more details.
I believe you can achieve this within the iron:router data context, by finding the post, associated author (whatever the field is), and then the subsequent user avatar. You can return an object to the iron:router data context. Then you can access post and avatar in the template as variables (so you might need to adjust the template output a little).
Publications.js
Meteor.publish("userPostAvatar", function() {
return Meteor.users.findOne( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
var post = Posts.findOne({_id: this.params._id});
var avatar = Users.findOne(post.authorId).profile.avatar;
return {
post: post,
avatar: avatar
};
}
});
Two problems with this method are that you could achieve the same thing with template helpers, and the user publication hasn't been limited to one user (I'm unsure how to do this unless we know the authorId within the waitOn, although maybe you could try moving the logic to there instead of the data context as my example shows).

Categories

Resources