Skip to content

Commit

Permalink
Merge dd71bef into 205320e
Browse files Browse the repository at this point in the history
  • Loading branch information
mircea-cosbuc committed Feb 11, 2019
2 parents 205320e + dd71bef commit 8887cfb
Show file tree
Hide file tree
Showing 5 changed files with 471 additions and 27 deletions.
107 changes: 89 additions & 18 deletions src/lib/client.js
Expand Up @@ -15,6 +15,7 @@ var StreamImageStore = require('./images');
var StreamReaction = require('./reaction');
var StreamUser = require('./user');
var jwtDecode = require('jwt-decode');
var assignIn = require('lodash/assignIn');

/**
* @callback requestCallback
Expand Down Expand Up @@ -857,25 +858,93 @@ StreamClient.prototype = {
* ]
* })
*/
if (data.foreignID) {
data['foreign_id'] = data.foreignID;
delete data.foreignID;
}
if (
data.id === undefined &&
(data.foreign_id === undefined || data.time === undefined)
) {
throw new TypeError('Missing id or foreign ID and time');
}
if (data.set && !(data.set instanceof Object)) {
throw new TypeError('set field should be an Object');
}
if (data.unset && !(data.unset instanceof Array)) {
throw new TypeError('unset field should be an Array');
}
return this.activitiesPartialUpdate([data], callback).then((response) => {
var activity = response.activities[0];
delete response.activities;
assignIn(activity, response);
return activity;
});
},

activitiesPartialUpdate: function(changes, callback) {
/**
* Update multiple activities with partial operations.
* @since
* @param {array} changes array containing the changesets to be applied. Every changeset contains the activity identifier which is either the ID or the pair of of foreign ID and time of the activity. The operations to issue can be set:{...} and unset:[...].
* @return {Promise}
* @xample
* client.activitiesPartialUpdate([
* {
* id: "4b39fda2-d6e2-42c9-9abf-5301ef071b12",
* set: {
* "product.price.eur": 12.99,
* "colors": {
* "blue": "#0000ff",
* "green": "#00ff00",
* },
* },
* unset: [ "popularity", "size.x2" ],
* },
* {
* id: "8d2dcad8-1e34-11e9-8b10-9cb6d0925edd",
* set: {
* "product.price.eur": 17.99,
* "colors": {
* "red": "#ff0000",
* "green": "#00ff00",
* },
* },
* unset: [ "rating" ],
* },
* ])
* @example
* client.activitiesPartialUpdate([
* {
* foreignID: "product:123",
* time: "2016-11-10T13:20:00.000000",
* set: {
* ...
* },
* unset: [
* ...
* ]
* },
* {
* foreignID: "product:321",
* time: "2016-11-10T13:20:00.000000",
* set: {
* ...
* },
* unset: [
* ...
* ]
* },
* ])
*/
if (!(changes instanceof Array)) {
throw new TypeError('changes should be an Array');
}
changes.forEach(function(item) {
if (!(item instanceof Object)) {
throw new TypeError(`changeset should be and Object`);
}
if (item.foreignID) {
item.foreign_id = item.foreignID;
}
if (
item.id === undefined &&
(item.foreign_id === undefined || item.time === undefined)
) {
throw new TypeError('missing id or foreign ID and time');
}
if (item.set && !(item.set instanceof Object)) {
throw new TypeError('set field should be an Object');
}
if (item.unset && !(item.unset instanceof Array)) {
throw new TypeError('unset field should be an Array');
}
});
var authToken;

if (this.usingApiSecret) {
authToken = signing.JWTScopeToken(this.apiSecret, 'activities', '*', {
feedId: '*',
Expand All @@ -888,7 +957,9 @@ StreamClient.prototype = {
return this.post(
{
url: 'activity/',
body: data,
body: {
changes: changes,
},
signature: authToken,
},
callback,
Expand Down
6 changes: 3 additions & 3 deletions src/lib/collections.js
Expand Up @@ -162,7 +162,7 @@ Collections.prototype = {
*
* @method upsert
* @memberof Collections.prototype
* @param {object or array} data - A single json object or an array of objects
* @param {object|array} data - A single json object or an array of objects
* @param {requestCallback} callback - Callback to call on completion
* @return {Promise} Promise object.
*/
Expand Down Expand Up @@ -200,7 +200,7 @@ Collections.prototype = {
*
* @method select
* @memberof Collections.prototype
* @param {object or array} ids - A single json object or an array of objects
* @param {object|array} ids - A single json object or an array of objects
* @param {requestCallback} callback - Callback to call on completion
* @return {Promise} Promise object.
*/
Expand Down Expand Up @@ -244,7 +244,7 @@ Collections.prototype = {
*
* @method delete
* @memberof Collections.prototype
* @param {object or array} ids - A single json object or an array of objects
* @param {object|array} ids - A single json object or an array of objects
* @param {requestCallback} callback - Callback to call on completion
* @return {Promise} Promise object.
*/
Expand Down
12 changes: 6 additions & 6 deletions src/lib/utils.js
Expand Up @@ -4,8 +4,8 @@ var validUserIdRe = /^[\w-]+$/;

function validateFeedId(feedId) {
/*
* Validate that the feedId matches the spec user:1
*/
* Validate that the feedId matches the spec user:1
*/
var parts = feedId.split(':');
if (parts.length !== 2) {
throw new errors.FeedError(
Expand All @@ -24,8 +24,8 @@ exports.validateFeedId = validateFeedId;

function validateFeedSlug(feedSlug) {
/*
* Validate that the feedSlug matches \w
*/
* Validate that the feedSlug matches \w
*/
var valid = validFeedSlugRe.test(feedSlug);
if (!valid) {
throw new errors.FeedError(
Expand All @@ -40,8 +40,8 @@ exports.validateFeedSlug = validateFeedSlug;

function validateUserId(userId) {
/*
* Validate the userId matches \w
*/
* Validate the userId matches \w
*/
var valid = validUserIdRe.test(userId);
if (!valid) {
throw new errors.FeedError(
Expand Down
168 changes: 168 additions & 0 deletions test/integration/cloud/activities.js
Expand Up @@ -138,3 +138,171 @@ describe('Get activities', () => {
});
});
});

describe('Update activities', () => {
let ctx = new CloudContext();
ctx.createUsers();
ctx.aliceAddsCheeseBurger();
let firstActivity;
let secondActivity;

describe('When alice prepares the cheese burger', () => {
let at = new Date().toISOString();

ctx.requestShouldNotError(async () => {
ctx.response = await ctx.alice.feed('user').addActivity({
verb: 'prepare',
object: ctx.cheeseBurger,
speed: 'slow',
ingredients: {
bread: 'white',
meat: 'beef',
vegetables: ['tomato', 'cucumber'],
},
foreign_id: 'fid:123',
time: at,
});
firstActivity = ctx.response;
});
});

describe('When bob eats the cheese burger', () => {
let at = new Date().toISOString();

ctx.requestShouldNotError(async () => {
ctx.response = await ctx.bob.feed('user').addActivity({
verb: 'eat',
object: ctx.cheeseBurger,
foreign_id: 'fid:321',
time: at,
rating: 'undecided',
expectations: 'low',
});
secondActivity = ctx.response;
});
});

describe('When partially updating the activity by ID', () => {
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.serverSideClient.activityPartialUpdate({
id: firstActivity.id,
set: { speed: 'fast', 'ingredients.meat': 'chicken' },
unset: [],
});
ctx.activity = ctx.response;
});
ctx.activityShouldHaveFields('speed', 'ingredients', 'duration');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(firstActivity.id);
ctx.activity.speed.should.equal('fast');
ctx.activity.ingredients.meat.should.equal('chicken');
});
});

describe('When partially updating the activity by Foreign ID', () => {
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.serverSideClient.activityPartialUpdate({
foreign_id: secondActivity.foreign_id,
time: secondActivity.time,
set: { speed: 'slow', rating: 'excellent' },
unset: ['expectations'],
});
ctx.activity = ctx.response;
});
ctx.activityShouldHaveFields('rating', 'speed', 'duration');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(secondActivity.id);
ctx.activity.speed.should.equal('slow');
ctx.activity.rating.should.equal('excellent');
ctx.activity.should.not.have.any.keys('expectations');
});
});

describe('When batch partially updating by ID', () => {
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.serverSideClient.activitiesPartialUpdate([
{
id: firstActivity.id,
set: { 'ingredients.bread': 'brown' },
unset: ['speed'],
},
{
id: secondActivity.id,
set: { rating: 'passable' },
unset: ['speed'],
},
]);
});
ctx.responseShould('contain multiple activities', () => {
ctx.response.should.have.all.keys('duration', 'activities');
ctx.response.activities.should.be.lengthOf(2);
});
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.alice
.feed('user')
.getActivityDetail(firstActivity.id);
});
ctx.responseShouldHaveActivityWithFields('ingredients');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(firstActivity.id);
ctx.activity.ingredients.bread.should.equal('brown');
ctx.activity.should.not.have.any.keys('speed');
});
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.bob
.feed('user')
.getActivityDetail(secondActivity.id);
});
ctx.responseShouldHaveActivityWithFields('rating');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(secondActivity.id);
ctx.activity.rating.should.equal('passable');
ctx.activity.should.not.have.any.keys('speed');
});
});

describe('When batch partially updating by foreign ID', () => {
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.serverSideClient.activitiesPartialUpdate([
{
foreign_id: firstActivity.foreign_id,
time: firstActivity.time,
set: { speed: 'dangerous' },
unset: ['ingredients.meat'],
},
{
foreign_id: secondActivity.foreign_id,
time: secondActivity.time,
set: { speed: 'light' },
unset: ['rating'],
},
]);
});
ctx.responseShould('contain multiple activities', () => {
ctx.response.should.have.all.keys('duration', 'activities');
ctx.response.activities.should.be.lengthOf(2);
});
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.alice
.feed('user')
.getActivityDetail(firstActivity.id);
});
ctx.responseShouldHaveActivityWithFields('ingredients', 'speed');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(firstActivity.id);
ctx.activity.speed.should.equal('dangerous');
ctx.activity.ingredients.should.not.have.any.keys('meat');
});
ctx.requestShouldNotError(async () => {
ctx.response = await ctx.bob
.feed('user')
.getActivityDetail(secondActivity.id);
});
ctx.responseShouldHaveActivityWithFields('speed');
ctx.activityShould('have updated fields', () => {
ctx.activity.id.should.equal(secondActivity.id);
ctx.activity.speed.should.equal('light');
ctx.activity.should.not.have.any.keys('rating');
});
});
});

0 comments on commit 8887cfb

Please sign in to comment.