I am trying to create e2e tests using Cypress for my Angular application. So far I managed to get the basics sorted, now I am trying to stub network requests.
I have created some fixtures and created a custom command:
Cypress.Commands.add('login', (email, password, happy = false) => {
let auth_url = Cypress.env('auth_url');
console.log(auth_url);
cy.get('[data-cy=email]').as('email');
cy.get('[data-cy=password]').as('password');
cy.get('[data-cy=login-form]').as('form');
cy.location('pathname').should('equal', '/login');
cy.get('#email').type(email);
cy.get('#password').type(password);
cy.get('#form').submit();
cy.fixture(happy ? 'successful-login.json' : 'failed-login.json').then((json) => {
console.log(json);
cy.intercept(`${auth_url}/connect/token`, json).then(() => {
if (!happy) return;
cy.fixture('current-user.json').then((currentUser) => {
console.log(currentUser);
cy.intercept(`/users/**`, currentUser);
});
});;
});
});
My fixtures are:
successful-login.json:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjRGNEVBQzE4N0Y5QUM1MTE4NjhFNTQ5OTA5RUIzOEQxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MTYwMDgxNTksImV4cCI6MTYxNjAxMTc1OSwiaXNzIjoiaHR0cHM6Ly9zeHAtZGV2ZWxvcC1pZGVudGl0eS5henVyZXdlYnNpdGVzLm5ldCIsImF1ZCI6WyJzeHAiLCJpZGVudGl0eS1zZXJ2ZXIiXSwiY2xpZW50X2lkIjoiY2xpZW50Iiwic3ViIjoiYTIzM2Q0MDgtMjVjMi00ODZhLWFlMzgtOTMzMTk5YjU2OWRkIiwiYXV0aF90aW1lIjoxNjE2MDA4MTU4LCJpZHAiOiJsb2NhbCIsInJvbGUiOiJBZG1pbmlzdHJhdG9yIiwicGVybWlzc2lvbiI6WyJ1c2VyczpyZWFkIiwidXNlcnM6d3JpdGUiXSwianRpIjoiQTRDNjREMzk0RTcyNEY3QUE1NzRFQUNEMjA2ODM4RkIiLCJpYXQiOjE2MTYwMDgxNTksInNjb3BlIjpbImlkZW50aXR5OnJlYWQiLCJpZGVudGl0eTp3cml0ZSIsInN4cDpyZWFkIiwic3hwOndyaXRlIl0sImFtciI6WyJwd2QiXX0.e-TX3u3o8UJZ9nq7nyuMFWfRyjEh55CwWQSHhfFxt677_qCu6pJ1cuHHOMjMV0lc-hTXjbe-4lZ7oOVfOZA_gByFo6mFpq8co__Npj1YpMt18LOSuewspKldffgCZQm2wIKTqSmMIQoFDRoUmAmcBsbJZIqSx4KMYBz7OfsBmdU7TeNi8bjtLcglzBxgYko7njJuC9j1EarR9BC-tjPIb0eoROclfQlPcYb05T4RRMzi3dpFExEXzFMaiMnR8_q0VOPWzeL0phFcjm-r1nVTVvrRcCtvI3GPPxr5EshT4XHcg6pmxx8BUlWXI6u1gSnWShmvXP5n87HJTCjsJh8sEQ",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "identity:read identity:write sxp:read sxp:write"
}
failed-login.json
{ "error": "invalid_grant", "error_description": "invalid_username_or_password" }
And finally, current-user.json:
{
"success": true,
"failure": false,
"error": null,
"result": {
"jobTitle": null,
"image": null,
"createdBy": "00000000-0000-0000-0000-000000000000",
"updatedBy": "00000000-0000-0000-0000-000000000000",
"dateCreated": "2021-02-26T11:23:18.074899",
"dateUpdated": "2021-02-26T11:23:18.0749379",
"deleted": false,
"roles": null,
"id": "a233d408-25c2-486a-ae38-933199b569dd",
"emailConfirmed": true,
"concurrencyStamp": "0246797d-fd93-4723-a142-95d5213f9ec7",
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnd": "2021-03-17T16:04:59.7781333+00:00",
"lockoutEnabled": true,
"accessFailedCount": 0
},
"message": null
}
The spec file looks like this:
/// <reference types="Cypress" />
describe('login workflow', () => {
it('does not work with wront credentials', () => {
const email = 'user#example.com';
const password = 'test';
cy.visit('/');
cy.login(email, password).as('loginRequest').then(() => {
cy.contains('.mat-simple-snackbar span', 'Invalid username or password.');
});
});
it('happy path test', () => {
const email = 'user#example.com';
const password = 'test';
cy.visit('/');
cy.location('pathname').should('equal', '/login');
cy.login(email, password, true).as('loginRequest').then(() => {
cy.location('pathname').should('equal', '/');
});
});
});
The issue I have is that even though the tests both say the routes are being stubbed:
The actual request, isn't:
The first test passes because the username and password are incorrect, even without stubbing the response, but the second test fails:
Because it doesn't stub the response:
Does anyone know what I am doing wrong?
Having a new intercept set up inside the .then() of a previous intercept seems like it might be a problem. There's nothing executing after that last intercept that would trigger it.
Since intercepts should be set up before the trigger occurs, the following should be (nearly) logically equivalent,
Cypress.Commands.add('login', (email, password, happy = false) => {
cy.intercept(`/users/**`, { fixture: 'current-user.json' });
const auth_url = Cypress.env('auth_url');
const loginFixture = happy ? 'successful-login.json' : 'failed-login.json';
cy.intercept(`${auth_url}/connect/token`, { fixture: loginFixture });
cy.get('[data-cy=email]').as('email');
cy.get('[data-cy=password]').as('password');
cy.get('[data-cy=login-form]').as('form');
cy.location('pathname').should('equal', '/login');
cy.get('#email').type(email);
cy.get('#password').type(password);
cy.get('#form').submit();
});
The only difference is the /users intercept is set up even if the auth intercept fails, but you are controlling that outcome anyway (with happy), so this should produce the same result
Related
I want to automate user creation in google as an Admin where I use app script to do it, however from the documentation I'm reading I'm not quite sure if I'm doing it right since I'm getting some errors in my code, like after POST and the Script not working.
function createUsers() {
const userjson = {
"primaryEmail": "atest#example.com",
"name": {
"givenName": "afirstName",
"familyName": "alastName"
},
"suspended": false,
"password": "pass2022",
"hashFunction": "SHA-1",
"changePasswordAtNextLogin": true,
"ipWhitelisted": false,
"orgUnitPath": "myOrgPath",
};
const optionalArgs = {
customer: 'my_customer',
orderBy: 'email'
};
POST https://admin.googleapis.com/admin/directory/v1/users
try {
const response = AdminDirectory.Users.list(optionalArgs);
const users = response.users;
//check if user exists
if (!users || users.length === 0)
//create new user
return AdminDirectory.newUser(userjson);
// Print user exists
Logger.log('User Existing');
} catch (err) {
// TODO (developer)- Handle exception from the Directory API
Logger.log('Failed with error %s', err.message);
}
}
As per the official documentation, if you want to do it with Google Apps Script, you should format your code as follows:
function createUsers() {
const userInfo = {
"primaryEmail": "jvd#domain.com",
"name": {
"givenName": "Jackie",
"familyName": "VanDamme"
},
"suspended": false,
"password": "thisisasupersecret",
"changePasswordAtNextLogin": true,
"ipWhitelisted": false
};
try{
AdminDirectory.Users.insert(userInfo);
console.log("User added");
} catch(error){
const {code, message} = error.details;
if(code === 409 && message === "Entity already exists."){
console.log("User already exists");
} else {
console.log(`${code} - ${message}`);
}
}
}
If you have any doubts about how to use the user resource payload, please refer to the official documentation of the REST API.
server side
first here is how my Api works
searchPosts(params) {
return Api().get('search', params);
},
var postRouter = express.Router();
postRouter.get('/search', (req, res) => {
var db = req.db;
Post.find({ capacity: { $lte : req.body.capacity } }, function (error, q) {
if (error) { console.error(error); }
res.send({q})
})
})
with this, in my express route im able to do GET request on /search with the body
{
"capacity":60
}
for exemple, and have a working-as-intended response
{
"q": [
{
"obj":"obj"
},
]
}
Website side
in my .Vue file i call this function after a clic on a button
<v-btn
:disabled="!formIsValid"
flat
color="primary"
type="submit"
#click="searchPost">Search</v-btn>
methods: {
async searchPost() {
const response = await PostsService.searchPosts({
capacity: this.form.capacity,
equipments: this.createObjectFromArray(this.form.equipments),
date: this.form.date,
time: this.form.time,
});
console.log(response);
this.availableList = response.q;
},
in postman i'm correctly getting a q[] array with all my filtered object inside but on chrome request have no q inside
for my Api logs, i'm getting no error with Postman but
message:
'Cast to number failed for value "undefined" at path "capacity" for model "Post"',
name: 'CastError',
...
on the real test on the web
for information, this.availableList is defined inside Data()
data() {
const defaultForm = Object.freeze({
capacity: 0,
date: new Date().toISOString().substr(0, 10),
time: null,
equipments: [],
});
return {
form: Object.assign({}, defaultForm),
datePicker: false,
hourMenu: false,
availableList: [],
allList: [],
defaultForm,
};
},
Make sure your capacity field in your Post model includes type: number. It probably assumes by default that you're submitting a string value if you do not declare the type as number explicitly in the model.
Also, because you are awaiting the response, you also have to ensure your data assignment waits for the response to return. Try this:
methods: {
async searchPost() {
const response = await PostsService.searchPosts({
capacity: this.form.capacity,
equipments: this.createObjectFromArray(this.form.equipments),
date: this.form.date,
time: this.form.time,
})
.then( response => {
console.log(response);
this.availableList = response.data.q;
}
)}
Notice that instead of response.q I used response.data.q. I can't test this at the moment, so if it doesn't work try switching back to response.q.
i've edited my request like so in my vuejs file
async searchPost() {
const response = await PostsService.searchPosts({
capacity: this.form.capacity,
equipments: this.createObjectFromArray(this.form.equipments),
date: this.form.date,
time: this.form.time,
});
this.availableList = response.data;
},
in my Api ive changed it like so
searchPosts(params) {
return Api().get('search', { params });
},
what a fool was i to try to run a get command by passing parameters inside the body and not the query string
and so, i've updated my get function accordingly
postRouter.get('/search', (req, res) => {
var db = req.db;
Post.find({ capacity: { $lte : req.query.capacity } }, 'title description capacity equipments resa', function (error, q) {
if (error) { return res.status(500).send(error); }
res.send(q);
})
})
to find my find parameter inside req.query and no more the body
I am trying to login a user.
I am calling the following API to check the credentials:
http://5c55225d84df580014cd06a3.mockapi.io/users?search=Ally45
Even though my call to my API appears to be returning correct data, my code isn't recognizing the returned data for some reason.
In my login-page.xml , I am calling the login() function when the user taps the login button:
login-view-model.js
// some code omitted for brevity...
function LoginViewModel() {
const viewModel = observableModule.fromObject({
processing: false,
email: "Ally45",
password: "12345",
login() {
this.processing = true;
userService.login({
email: this.email,
password: this.password
}).then(() => {
this.processing = false;
topmost().navigate("./home/home-page");
})
.catch((e) => {
this.processing = false;
alert("Unfortunately we could not find your account.");
});
},
});
return viewModel;
}
so I simply want to log the user in , then go to home page.
Here is my userService.login function that is being called above:
user-service.js
exports.login = function (user) {
return new Promise((resolve, reject) => {
httpModule.getJSON("http://5c55225d84df580014cd06a3.mockapi.io/users?search=" + user.email)
.then( resolve(response))
.catch((error) => { handleErrors(error); reject(); })
})
};
( For testing, I am only checking to see if the email exists "Ally45" in this example , as the JSON is returning )
Results Being Returned
I should be being re-directed to the home screen but I never get there.
Does anyone see where I am going wrong?
Thanks. So stuck...
John
Your Http fetch doesn't have any issues, it's only the way you are processing the response. When you hit that URL you get the below object on your result
[
{
"id": "43",
"createdAt": "2019-02-01T05:54:43.766Z",
"username": "Ally45"
}
]
Which is an array but you are testing it as result.id === undefined, obviously it's undefined and you are calling reject()
Your condition suppose to be result.length === 0 || result[0].id === void 0
Updated Playground
I'm using AWS Amplify for authentication and Stripe for the payment to create sign up page.
PROBLEM: I can't find a way to combine validations for Email and password section(from AWS Amplify) with payment info section(from Stripe).
My current code creates a Stripe token and call API(with valid payment info) then handles the error message from userSignupRequest which takes care of email and password fields.
How do I validate the email and password with payment info then create account in AWS and Stripe?
// Stripe payment process
this.props.stripe.createToken(
{
email: this.state.email
}
).then(result => {
// PROBLEM: Form server validation from Stripe
if(result.error){
return this.setState({ errors: { errorMsg: result.error.message }, isLoading: false })
}
// if success, create customer and subscription with result.token.id
const apiName = 'NameOfAPI';
const path = '/stripe/signup';
let myInit = {
body: {
"stripeToken": result.token.id,
"email": this.state.email
}
}
API.post(apiName , path, myInit).then(reponse => {
this.props.userSignupRequest(this.state.email, this.state.password, reponse).then(user => {
this.setState({
confirmAccount: true,
isLoading: false,
userEmail: this.state.email,
errors: {}
})
this.props.history.push('/signup#confirm-account')
}).catch(err => {
// PROBLEM: Form server validation
this.setState({ errors: { errorMsg: err.message }, isLoading: false })
})
}).catch(err => {
console.log(err)
this.setState({ errors: { errorMsg: err }, isLoading: false })
});
})
It seems like we have a very similar stack. My solution was to handle everything server-side. You'll need to give your lambda functions the appropriate IAM permissions to access Cognito. The code below is a little long. I use async/await, which really cleans things up for me. You'll need to use Lambda with node 8 to use async/await though.
I validate that everything matches the right format client-side (i.e. emails are really emails, passwords are the right length). I realized the only error that could come up is an "existing user" error from Cognito. The idea is: test if the user exists before you attempt to sign the person up with Stripe. There's no way to "test" if the user's credit card is valid with Stripe. It's all or nothing. If it's valid it will go through, if not, you'll get an error. If it goes through, you can then sign up the user with Cognito, knowing you should not get an error (you've validated the email and password client-side and, you know the use doesn't already exist).
For reference, here's the aws-sdk for cognito.
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({
region: "region",
userPoolId: "cognito_user_pool_id",
});
module.exports.signUpUser = (payload) => {
const usernamePayload = {
UserPoolId: "cognito_user_pool_id",
Username: payload.email,
};
// I use emails for usernames.
new Promise((resolve, reject) => {
cognito.adminGetUser(usernamePayload, (error, response) => {
if (error && error.code === 'UserNotFoundException') {
resolve(false);
} else if (error) {
reject(error);
} else {
// if adminGetUser doesn't fail, it means the username exists
resolve(true);
}
});
}).then((usernameExists) => {
if (!usernameExists) {
// run stripe API stuff
// always run before sign up below to catch stripe errors
// and return those errors to client
// before you sign up the user to Cognito
// since you've already verified the user does not exist
// it would be rare for an error to come up here
// as long as you validate passwords and emails client-side
const signUpPayload = {
ClientId: "cognito_user_pool_client_id",
Username: payload.email,
Password: payload.password,
UserAttributes: [
{
Name: 'email',
Value: payload.email,
},
],
};
new Promise((resolve, reject) => {
cognito.signUp(signUpPayload, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
}).catch((error) => {
// you should hopefully encounter no errors here
// once you get everything setup correctly
console.log(error);
})
} else {
// means username already exists, send error to client
// saying username exists
}
}).catch((error) => {
// may want to dispatch this error to client
console.log(error);
});
return null;
};
We have the following scenario:
Call server.exists()
Select user vertices
Call server.exists()
The result is a failure on the 3rd step.
More details and a reduced use-case:
const OrientDB = require('orientjs')
const server = OrientDB({
"host": "localhost",
"port": 2424,
"username": "root",
"password": "password",
"pool": {
"max": 10
}
})
const db = server.use('myDatabase')
server.exists(db.name, db.type).then(exists => {
console.log("Database exists first time: " + exists)
db.select().from('user').column('id', 'name').all().then(() => {
server.exists(db.name, db.type).then(exists => {
console.log("Database exists second time: " + exists)
}).catch(err => {
console.log("Error on second exists:", err)
})
})
})
I get the following error on the second server.exists() call:
{
[OrientDB.RequestError: Server user not authenticated.]
name: 'OrientDB.RequestError',
message: 'Server user not authenticated.',
data: {},
previous: [],
id: 1,
type: 'com.orientechnologies.orient.core.exception.OSecurityAccessException',
hasMore: 0
}
This isn't the actual code, it's reduced but highlights the error. The real code is spread over three files in an API.
I have tested this on OrientDB 2.1.3 and 2.1.5, both on Mac and Ubuntu.
Any thoughts on why this happens are welcome.
Thanks.