pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
419 lines (358 loc) • 14 kB
text/typescript
/* global describe, it, beforeEach */
import assert from 'assert';
import { SetUUIDMembershipsRequest } from '../../../src/core/endpoints/objects/membership/set';
import { TransportMethod } from '../../../src/core/types/transport-request';
import RequestOperation from '../../../src/core/constants/operations';
import { KeySet } from '../../../src/core/types/api';
import * as AppContext from '../../../src/core/types/api/app-context';
describe('SetUUIDMembershipsRequest (Remove Operation)', () => {
let defaultKeySet: KeySet;
let defaultParameters: AppContext.SetMembershipsParameters<AppContext.CustomData> & { keySet: KeySet; type: 'delete' };
beforeEach(() => {
defaultKeySet = {
publishKey: 'test_publish_key',
subscribeKey: 'test_subscribe_key',
};
defaultParameters = {
uuid: 'test_user_uuid',
channels: ['channel1'],
type: 'delete',
keySet: defaultKeySet,
};
});
describe('parameter validation', () => {
it("should return \"'uuid' cannot be empty\" when uuid is empty", () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
uuid: '',
});
assert.equal(request.validate(), "'uuid' cannot be empty");
});
it('should return "Channels cannot be empty" when channels is empty array', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: [],
});
assert.equal(request.validate(), 'Channels cannot be empty');
});
it('should return "Channels cannot be empty" when channels is null', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: null as any,
});
assert.equal(request.validate(), 'Channels cannot be empty');
});
it('should return undefined when required parameters provided', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
uuid: 'test',
channels: ['channel1'],
});
assert.equal(request.validate(), undefined);
});
});
describe('operation type', () => {
it('should return correct operation type', () => {
const request = new SetUUIDMembershipsRequest(defaultParameters);
assert.equal(request.operation(), RequestOperation.PNSetMembershipsOperation);
});
});
describe('URL construction', () => {
it('should construct correct path for remove operation', () => {
const request = new SetUUIDMembershipsRequest(defaultParameters);
const transportRequest = request.request();
const expectedPath = `/v2/objects/${defaultKeySet.subscribeKey}/uuids/${defaultParameters.uuid}/channels`;
assert.equal(transportRequest.path, expectedPath);
});
});
describe('HTTP method', () => {
it('should use PATCH method', () => {
const request = new SetUUIDMembershipsRequest(defaultParameters);
const transportRequest = request.request();
assert.equal(transportRequest.method, TransportMethod.PATCH);
});
});
describe('headers', () => {
it('should set correct Content-Type header', () => {
const request = new SetUUIDMembershipsRequest(defaultParameters);
const transportRequest = request.request();
assert.equal(transportRequest.headers?.['Content-Type'], 'application/json');
});
});
describe('request body', () => {
it('should create proper body for remove operation', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: ['channel1', 'channel2'],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
const expectedBody = {
delete: [
{ channel: { id: 'channel1' } },
{ channel: { id: 'channel2' } }
]
};
assert.deepEqual(body, expectedBody);
});
it('should create proper body for object channels in remove operation', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: [{
id: 'channel1',
custom: { role: 'admin' },
status: 'active',
type: 'member'
}],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
const expectedBody = {
delete: [{
channel: { id: 'channel1' },
status: 'active',
type: 'member',
custom: { role: 'admin' }
}]
};
assert.deepEqual(body, expectedBody);
});
it('should handle mixed string and object channels for delete', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: [
'channel1',
{ id: 'channel2', status: 'active' }
],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
const expectedBody = {
delete: [
{ channel: { id: 'channel1' } },
{
channel: { id: 'channel2' },
status: 'active'
// undefined values are omitted by JSON.stringify
}
]
};
assert.deepEqual(body, expectedBody);
});
});
describe('backward compatibility', () => {
it('should map userId to uuid parameter', () => {
const request = new SetUUIDMembershipsRequest({
keySet: defaultKeySet,
userId: 'test-user-id', // Using userId instead of uuid
channels: ['channel1'],
type: 'delete',
// uuid is not provided
} as any);
// The request should be valid as userId gets mapped to uuid internally
assert.equal(request.validate(), undefined);
const transportRequest = request.request();
const expectedPath = `/v2/objects/${defaultKeySet.subscribeKey}/uuids/test-user-id/channels`;
assert.equal(transportRequest.path, expectedPath);
});
});
describe('remove operation specifics', () => {
it('should use delete key in request body', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: ['channel1', 'channel2', 'channel3'],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert(body.hasOwnProperty('delete'));
assert(!body.hasOwnProperty('set'));
assert.equal(body.delete.length, 3);
});
it('should handle single channel removal', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: ['single-channel'],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert.equal(body.delete.length, 1);
assert.equal(body.delete[0].channel.id, 'single-channel');
});
it('should handle bulk channel removal', () => {
const channels = Array.from({ length: 50 }, (_, i) => `channel_${i}`);
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels,
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert.equal(body.delete.length, 50);
channels.forEach((channel, index) => {
assert.equal(body.delete[index].channel.id, channel);
});
});
});
describe('query parameters for delete operation', () => {
it('should include default include flags', () => {
const request = new SetUUIDMembershipsRequest(defaultParameters);
const transportRequest = request.request();
const queryParams = transportRequest.queryParameters;
// Default include flags as per source code
const includeParam = queryParams?.include as string;
assert(includeParam?.includes('channel.status'));
assert(includeParam?.includes('channel.type'));
assert(includeParam?.includes('status'));
});
it('should handle pagination parameters with delete', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
page: {
next: 'nextToken',
},
});
const transportRequest = request.request();
const queryParams = transportRequest.queryParameters;
assert.equal(queryParams?.start, 'nextToken');
});
it('should handle include options with delete', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
include: {
customFields: true,
channelFields: true,
totalCount: true,
},
});
const transportRequest = request.request();
const queryParams = transportRequest.queryParameters;
const includeString = queryParams?.include as string;
assert(includeString.includes('custom'));
assert(includeString.includes('channel'));
assert.equal(queryParams?.count, 'true');
});
});
describe('channel removal edge cases', () => {
it('should handle channels with special characters', () => {
const specialChannels = [
'channel#1@domain.com',
'channel with spaces',
'channel/with/slashes',
];
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: specialChannels,
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
specialChannels.forEach((channelId, index) => {
assert.equal(body.delete[index].channel.id, channelId);
});
});
it('should handle empty custom objects in remove', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: [{
id: 'channel1',
custom: {},
status: 'inactive'
}],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert.deepEqual(body.delete[0].custom, {});
assert.equal(body.delete[0].status, 'inactive');
});
it('should handle null values in channel objects for delete', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: [{
id: 'channel1',
custom: null as any,
status: null as any
}],
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert.equal(body.delete[0].custom, null);
assert.equal(body.delete[0].status, null);
});
});
describe('comprehensive validation tests', () => {
it('should handle uuid with special characters for delete', () => {
const specialUuid = 'user@domain.com#123';
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
uuid: specialUuid,
});
const transportRequest = request.request();
assert(transportRequest.path.includes(encodeURIComponent(specialUuid)));
});
it('should maintain request isolation between set and delete', () => {
const setRequest = new SetUUIDMembershipsRequest({
...defaultParameters,
type: 'set',
});
const deleteRequest = new SetUUIDMembershipsRequest({
...defaultParameters,
type: 'delete',
});
const setBody = JSON.parse(setRequest.request().body as string);
const deleteBody = JSON.parse(deleteRequest.request().body as string);
assert(setBody.hasOwnProperty('set'));
assert(!setBody.hasOwnProperty('delete'));
assert(deleteBody.hasOwnProperty('delete'));
assert(!deleteBody.hasOwnProperty('set'));
});
});
describe('large dataset handling', () => {
it('should handle removal of many channels', () => {
const manyChannels = Array.from({ length: 100 }, (_, i) => ({
id: `channel_${i}`,
status: i % 2 === 0 ? 'active' : 'inactive',
custom: { index: i }
}));
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
channels: manyChannels,
type: 'delete',
});
const transportRequest = request.request();
const body = JSON.parse(transportRequest.body as string);
assert.equal(body.delete.length, 100);
assert.equal(body.delete[0].channel.id, 'channel_0');
assert.equal(body.delete[99].channel.id, 'channel_99');
assert.deepEqual(body.delete[50].custom, { index: 50 });
});
});
describe('filter and sort parameters for delete', () => {
it('should handle filter parameter with delete operation', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
filter: 'channel.status == "active"',
});
const transportRequest = request.request();
const queryParams = transportRequest.queryParameters;
assert.equal(queryParams?.filter, 'channel.status == "active"');
});
it('should handle sort parameter with delete operation', () => {
const request = new SetUUIDMembershipsRequest({
...defaultParameters,
sort: { 'channel.updated': 'desc' },
});
const transportRequest = request.request();
const queryParams = transportRequest.queryParameters;
const sortArray = queryParams?.sort as string[];
assert(Array.isArray(sortArray));
assert(sortArray.includes('channel.updated:desc'));
});
});
});