Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,97 @@ describe('Cloud Code', () => {
});
});

it('can return custom HTTP status code', async () => {
Parse.Cloud.define('customStatus', (req, res) => {
res.status(201);
return { message: 'Created' };
});

const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/functions/customStatus',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {},
});

expect(response.status).toEqual(201);
expect(response.data.result.message).toEqual('Created');
});

it('can return custom HTTP headers', async () => {
Parse.Cloud.define('customHeaders', (req, res) => {
res.set('X-Custom-Header', 'custom-value');
res.set('X-Another-Header', 'another-value');
return { success: true };
});

const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/functions/customHeaders',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {},
});

expect(response.status).toEqual(200);
expect(response.headers['x-custom-header']).toEqual('custom-value');
expect(response.headers['x-another-header']).toEqual('another-value');
expect(response.data.result.success).toEqual(true);
});

it('can return custom HTTP status code and headers together', async () => {
Parse.Cloud.define('customStatusAndHeaders', (req, res) => {
res.status(401).set('WWW-Authenticate', 'Bearer realm="api"');
return { error: 'Authentication required' };
});

try {
await request({
method: 'POST',
url: 'http://localhost:8378/1/functions/customStatusAndHeaders',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {},
});
fail('Expected request to reject with 401');
} catch (response) {
expect(response.status).toEqual(401);
expect(response.headers['www-authenticate']).toEqual('Bearer realm="api"');
expect(response.data.result.error).toEqual('Authentication required');
}
});

it('returns normal response when response object is not used', async () => {
Parse.Cloud.define('normalResponse', () => {
return { status: 201, result: 'this should be the result' };
});

const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/functions/normalResponse',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {},
});

expect(response.status).toEqual(200);
expect(response.data.result.status).toEqual(201);
expect(response.data.result.result).toEqual('this should be the result');
});

it('can get config', () => {
const config = Parse.Server;
let currentConfig = Config.get('test');
Expand Down
58 changes: 53 additions & 5 deletions src/Routers/FunctionsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,41 @@ import { jobStatusHandler } from '../StatusHandler';
import _ from 'lodash';
import { logger } from '../logger';

class CloudResponse {
constructor() {
this._status = null;
this._headers = {};
}

status(code) {
if (typeof code !== 'number') {
throw new Error('Status code must be a number');
}
this._status = code;
return this;
}

set(name, value) {
if (typeof name !== 'string') {
throw new Error('Header name must be a string');
}
this._headers[name] = value;
return this;
}

hasCustomResponse() {
return this._status !== null || Object.keys(this._headers).length > 0;
}

getStatus() {
return this._status;
}

getHeaders() {
return this._headers;
}
}

function parseObject(obj, config) {
if (Array.isArray(obj)) {
return obj.map(item => {
Expand Down Expand Up @@ -103,14 +138,25 @@ export class FunctionsRouter extends PromiseRouter {
});
}

static createResponseObject(resolve, reject) {
static createResponseObject(resolve, reject, cloudResponse) {
return {
success: function (result) {
resolve({
const response = {
response: {
result: Parse._encode(result),
},
});
};
if (cloudResponse && cloudResponse.hasCustomResponse()) {
const status = cloudResponse.getStatus();
const headers = cloudResponse.getHeaders();
if (status !== null) {
response.status = status;
}
if (Object.keys(headers).length > 0) {
response.headers = headers;
}
}
resolve(response);
},
error: function (message) {
const error = triggers.resolveError(message);
Expand All @@ -128,6 +174,7 @@ export class FunctionsRouter extends PromiseRouter {
}
let params = Object.assign({}, req.body, req.query);
params = parseParams(params, req.config);
const cloudResponse = new CloudResponse();
const request = {
params: params,
config: req.config,
Expand Down Expand Up @@ -182,14 +229,15 @@ export class FunctionsRouter extends PromiseRouter {
} catch (e) {
reject(e);
}
}
},
cloudResponse
);
return Promise.resolve()
.then(() => {
return triggers.maybeRunValidator(request, functionName, req.auth);
})
.then(() => {
return theFunction(request);
return theFunction(request, cloudResponse);
})
.then(success, error);
});
Expand Down