What format should I use for payload and headers in my chai REST-API test? - rest

I am setting up REST-API test within my codecept testing framework which uses integrated Chai.
After checking the very basic documentation on the subject in CodeceptJS documentation I can't seem to get my test to work.
const expect = require('chai').expect;
Feature('Checkout');
Scenario('Create a customer', async I => {
const payload = ({
first_name: 'Dummy title',
last_name: 'Dummy description',
email: 'john#test.com',
locale: 'fr-CA',
billing_address[first_name]: 'John',
billing_address[last_name]: 'Doe',
billing_address[line1]: 'PO Box 9999',
billing_address[city]: 'Walnut',
billing_address[state]: 'California',
billing_address[zip]: '91789',
billing_address[country]: 'US'
})
const header = ({Content-Type: 'application/x-www-form-urlencoded'})
const res = await I.sendPostRequest('https://mytesturl-
api.com/api/',payload,header);
expect(res.code).eql(200);
});
I have put my payload and header in a variable for ease of use and readability.
But it doesn't work and keeps giving me Unexpected token [

I figured it out.
The way to format the payload was as a string (see example below)
const expect = require('chai').expect;
Feature('Checkout');
Scenario('Create a customer', async I => {
const payload = ({
first_name: 'Dummy title',
last_name: 'Dummy description',
email: 'john#test.com',
locale: 'fr-CA',
'billing_address[first_name]': 'John',
'billing_address[last_name]': 'Doe',
'billing_address[line1]': 'PO Box 9999',
'billing_address[city]': 'Walnut',
'billing_address[state]': 'California',
'billing_address[zip]': '91789',
'billing_address[country]': 'US'
})
const header = ({Content-Type: 'application/x-www-form-urlencoded'})
const res = await I.sendPostRequest('https://mytesturl-api.com/api/',payload,header);
expect(res.code).eql(200);
});

Related

Redux Toolkit Query: Reduce state from "mutation" response

Let's say I have an RESTish API to manage "posts".
GET /posts returns all posts
PATCH /posts:id updates a post and responds with new record data
I can implement this using RTK query via something like this:
const TAG_TYPE = 'POST';
// Define a service using a base URL and expected endpoints
export const postsApi = createApi({
reducerPath: 'postsApi',
tagTypes: [TAG_TYPE],
baseQuery,
endpoints: (builder) => ({
getPosts: builder.query<Form[], string>({
query: () => `/posts`,
providesTags: (result) =>
[
{ type: TAG_TYPE, id: 'LIST' },
],
}),
updatePost: builder.mutation<any, { formId: string; formData: any }>({
// note: an optional `queryFn` may be used in place of `query`
query: (data) => ({
url: `/post/${data.formId}`,
method: 'PATCH',
body: data.formData,
}),
// this causes a full re-query.
// Would be more efficient to update state based on resp.body
invalidatesTags: [{ type: TAG_TYPE, id: 'LIST' }],
}),
}),
});
When updatePost runs, it invalidates the LIST tag which causes getPosts to run again.
However, since the PATCH operation responds with the new data itself, I would like to avoid making an additional server request and instead just update my reducer state for that specific record with the content of response.body.
Seems like a common use case, but I'm struggling to find any documentation on doing something like this.
You can apply the mechanism described in optimistic updates, just a little bit later:
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query'
import { Post } from './types'
const api = createApi({
// ...
endpoints: (build) => ({
// ...
updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
query: ({ id, ...patch }) => ({
// ...
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled
dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, data)
})
)
},
}),
}),
})

How to schedule notification at specific time using awesome package in Flutter?

I am using an awesome Package and am trying to make a notification at a specific time using this package.
Future<void> showNotificationWithIconsAndActionButtons(int id) async {
AwesomeNotifications().initialize(
'',
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: Color(0xFF9D50DD),
ledColor: Colors.white,
playSound: true,
importance: NotificationImportance.Max,
defaultRingtoneType: DefaultRingtoneType.Notification,
)
]
);
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'basic_channel',
title: 'Anonymous says:',
body: 'Hi there!',
payload: {'uuid': 'user-profile-uuid'},
displayOnBackground: true,
displayOnForeground: true,
),
i need to make notification in particular time.
The same plugin provides a way to schedule notifications as per the need.
Checkout this link from it's description:
https://pub.dev/packages/awesome_notifications#scheduling-a-notification
Bascially showing it at a specific time will look something like this:
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'scheduled',
title: 'Just in time!',
body: 'This notification was schedule to shows at ' +
(Utils.DateUtils.parseDateToString(scheduleTime.toLocal()) ?? '?') +
' $timeZoneIdentifier (' +
(Utils.DateUtils.parseDateToString(scheduleTime.toUtc()) ?? '?') +
' utc)',
notificationLayout: NotificationLayout.BigPicture,
bigPicture: 'asset://assets/images/delivery.jpeg',
payload: {'uuid': 'uuid-test'},
autoCancel: false,
),
schedule: NotificationCalendar.fromDate(date: scheduleTime));
Note: The code sample is from the plugin's Readme. I have not tested this yet.
Timer.periodic(Duration(minutes: 1), (timer) {
if (DateTime.now()== DateTime.parse("2021-07-20 20:18:04Z")){// 8:18pm
return showNotificationWithIconsAndActionButtons();
}
});
This will let it check the time every minute.

Google Action not deploying

I'm trying to get a simple audio stream playing in a Google Action cloud function. It won't deploy as it says there are errors in the code, but it's not saying where. Here's my code (note the alternatives in *** comments - confusing as I'm not sure which I should be using, having seen both!
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
const { MediaObject, SimpleResponse } = require('actions-on-google');
const app = actionssdk() // *** or 'const app = conversation()' ?
app.handle("playStream", conv => { // *** or 'app.intent(...' ?
conv.ask(new SimpleResponse("Playing stream"));
conv.ask(new MediaObject({
name: 'My stream',
url: 'https://stream.redacted.url',
description: 'The stream',
icon: new Image({
url: 'https://image.redacted.url', alt: 'Media icon',
}),
}));
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
You seem to be using a mix of actions-on-google and #assistant/conversation libraries. You should only be using one or the other, depending on what platform you are using.
If you are using the original Actions SDK, you should use actions-on-google. If you are using the new Actions SDK or Actions Builder, you should use #assistant/conversation.
The distinctions are different but will result in problems. actionssdk has an intent method but not a handle method, and vice versa for conversation. This can result in a syntax error. Also make sure you've imported everything, including Image.
const functions = require('firebase-functions')
const {
conversation,
Media,
Image,
} = require('#assistant/conversation')
const app = conversation()
app.handle("playStream", conv => {
conv.add("Playing stream");
conv.add(new Media({
mediaObjects: [{
name: 'Media name',
description: 'Media description',
url: 'https://actions.google.com/sounds/v1/cartoon/cartoon_boing.ogg',
image: {
large: new Image({
url: 'https://image.redacted.url',
alt: 'Media icon',
}),
}
}],
mediaType: 'AUDIO',
optionalMediaControls: ['PAUSED', 'STOPPED']
}));
})
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app)
If this still doesn't work, you should add what errors you're seeing in trying to deploy.

How to validate for ObjectID

Using Joi schema validation, is it possible to validate against MongoDB ObjectID's?
Something like this could be great:
_id: Joi.ObjectId().required().error(errorParser),
const Joi = require('#hapi/joi')
Joi.objectId = require('joi-objectid')(Joi)
const schema = Joi.object({
id: Joi.objectId(),
name: Joi.string().max(100),
date: Joi.date()
})
checkout https://www.npmjs.com/package/joi-objectid
I find that if I do
Joi.object({
id: Joi.string().hex().length(24)
})
it works without installing any external library or using RegEx
The hex makes sure the string contains only hexadecimal characters and the length makes sure that it is a string of exactly 24 characters
This package works if you are using the new version of Joi.
const Joi = require('joi-oid')
const schema = Joi.object({
id: Joi.objectId(),
name: Joi.string(),
age: Joi.number().min(18),
})
package: https://www.npmjs.com/package/joi-oid
If you want a TypeScript version of the above library integrated with Express without installing anything:
import Joi from '#hapi/joi';
import { createValidator } from 'express-joi-validation';
export const JoiObjectId = (message = 'valid id') => Joi.string().regex(/^[0-9a-fA-F]{24}$/, message)
const validator = createValidator({
passError: true,
});
const params = Joi.object({
id: JoiObjectId().required(),
});
router.get<{ id: string }>(
'/:id',
validator.params(params),
(req, res, next) => {
const { id } = req.params; // id has string type
....
}
);
I share mine
let id =
Joi.string()
.regex(/^[0-9a-fA-F]{24}$/)
.message('must be an oid')
With joi naked package you can use this:
const ObjectId = joi.object({
id: joi.binary().length(12),
})
This is a core function without any external package. I have used Joi and mongoose packages to achieve it.
const Joi = require("joi");
const ObjectID = require("mongodb").ObjectID;
const schema = Joi.object({
id: Joi.string().custom((value, helpers) => {
const filtered = ObjectID.isValid(value)
return !filtered ? helpers.error("any.invalid") : value;
},
"invalid objectId", ).required(),
name: Joi.string(),
age: Joi.number().min(18),
})
I see many correct answers here, but I also want to express my opinion.
I am not against installing npm packages, but installing one package just to validate an object id is overkill. I know programmers are lazy but C'mooon :)
Here is my full code function that validates two object id properties, very simple:
function validateRental(rental) {
const schema = Joi.object({
movieId: Joi.string()
.required()
.regex(/^[0-9a-fA-F]{24}$/, 'object Id'),
customerId: Joi.string()
.required()
.regex(/^[0-9a-fA-F]{24}$/, 'object Id'),
})
const { error } = schema.validate(rental)
return {
valid: error == null,
message: error ? error.details[0].message : null,
}
}
This way, if any of the properties contain invalid id like this:
{
"movieId": "123456",
"customerId": "63edf556f383d108d54a68a0"
}
This will be the error message:
`"movieId" with value "123456" fails to match the object Id pattern`

Create user with avatar

I want to add an avatar in the user registration, but I don't know how, Please can someone share with me a full example (form, JS front, and JS backend). I'm using SailsJS 1.0 (the stable version) with VueJs.
Thanks in advance .
I figured it out. Watch these platzi tutorials:
https://courses.platzi.com/classes/1273-sails-js/10757-uploading-backend-file/
https://courses.platzi.com/classes/1273-sails-js/10758-uploading-frontend-files/
https://courses.platzi.com/classes/1273-sails-js/10759-downloading-files/
Here is what the videos tell you to do:
npm i sails-hook-uploads.
In api/controllers/entrance/signup.js
Above inputs key add a new key/value of files: ['avatar'],
In the inputs add:
avatar: {
type: 'ref',
required: true
}
In the body of the fn find var newUserRecord and above this add (even if avatar is not required, make sure to do this line, otherwise you will have a "timeout of unconsuemd file stream":
const avatarInfo = await sails.uploadOne(inputs.avatar);
Then in the first argument object of var newUserRecord = await User.create(_.extend({ add:
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
In api/models/User.js, add these attributes to your User model:
avatarFd: {
type: 'string',
required: false,
description: 'will either have "text" or "avatarFd"'
},
avatarMime: {
type: 'string',
required: false,
description: 'required if "avatarFd" provided'
},
Then create a download endpoint, here is how the action would look for it:
const user = await User.findOne(id);
this.res.type(paste.photoMime);
const avatarStream = await sails.startDownload(paste.photoFd);
return exits.success(avatarStream);
Add to the routes a route for this download avatar endpoint.
Then you can display this avatar by pointing the <img src=""> the source in here to this download endpoint.
------APPENDIX-----
----signup.js-----
module.exports = {
friendlyName: 'Signup',
description: 'Sign up for a new user account.',
extendedDescription:
`This creates a new user record in the database, signs in the requesting user agent
by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
(if emailing with Mailgun is enabled) sends an account verification email.
If a verification email is sent, the new user's account is put in an "unconfirmed" state
until they confirm they are using a legitimate email address (by clicking the link in
the account verification message.)`,
files: ['avatar'],
inputs: {
emailAddress: {
required: true,
type: 'string',
isEmail: true,
description: 'The email address for the new account, e.g. m#example.com.',
extendedDescription: 'Must be a valid email address.',
},
password: {
required: true,
type: 'string',
maxLength: 200,
example: 'passwordlol',
description: 'The unencrypted password to use for the new account.'
},
fullName: {
required: true,
type: 'string',
example: 'Frida Kahlo de Rivera',
description: 'The user\'s full name.',
},
avatar: {
}
},
exits: {
success: {
description: 'New user account was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided fullName, password and/or email address are invalid.',
extendedDescription: 'If this request was sent from a graphical user interface, the request '+
'parameters should have been validated/coerced _before_ they were sent.'
},
emailAlreadyInUse: {
statusCode: 409,
description: 'The provided email address is already in use.',
},
},
fn: async function (inputs) {
var newEmailAddress = inputs.emailAddress.toLowerCase();
// must do this even if inputs.avatar is not required
const avatarInfo = await sails.uploadOne(inputs.avatar);
// Build up data for the new user record and save it to the database.
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
var newUserRecord = await User.create(_.extend({
emailAddress: newEmailAddress,
password: await sails.helpers.passwords.hashPassword(inputs.password),
fullName: inputs.fullName,
tosAcceptedByIp: this.req.ip,
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
}, sails.config.custom.verifyEmailAddresses? {
emailProofToken: await sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
emailStatus: 'unconfirmed'
}:{}))
.intercept('E_UNIQUE', 'emailAlreadyInUse')
.intercept({name: 'UsageError'}, 'invalid')
.fetch();
// If billing feaures are enabled, save a new customer entry in the Stripe API.
// Then persist the Stripe customer id in the database.
if (sails.config.custom.enableBillingFeatures) {
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
emailAddress: newEmailAddress
}).timeout(5000).retry();
await User.updateOne(newUserRecord.id)
.set({
stripeCustomerId
});
}
// Store the user's new id in their session.
this.req.session.userId = newUserRecord.id;
if (sails.config.custom.verifyEmailAddresses) {
// Send "confirm account" email
await sails.helpers.sendTemplateEmail.with({
to: newEmailAddress,
subject: 'Please confirm your account',
template: 'email-verify-account',
templateData: {
fullName: inputs.fullName,
token: newUserRecord.emailProofToken
}
});
} else {
sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
}
// add to pubilc group
const publicGroup = await Group.fetchPublicGroup();
await Group.addMember(publicGroup.id, newUserRecord.id);
}
};