UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

407 lines (341 loc) 15.1 kB
/* global describe, it, beforeEach */ import assert from 'assert'; import { RevokeTokenRequest } from '../../../src/core/endpoints/access_manager/revoke_token'; import { TransportResponse } from '../../../src/core/types/transport-response'; import { TransportMethod } from '../../../src/core/types/transport-request'; import RequestOperation from '../../../src/core/constants/operations'; import { KeySet } from '../../../src/core/types/api'; describe('RevokeTokenRequest', () => { let defaultKeySet: KeySet; let defaultParameters: { token: string; keySet: KeySet }; beforeEach(() => { defaultKeySet = { publishKey: 'test_publish_key', subscribeKey: 'test_subscribe_key', secretKey: 'test_secret_key', }; defaultParameters = { token: 'p0AkFl043rhDdHRsple3KgQ3NwY6BDcENnctokenVzcqBDczaWdYIGOAeTyWGJI', keySet: defaultKeySet, }; }); describe('validation', () => { it('should validate required secret key', () => { const requestWithoutSecretKey = new RevokeTokenRequest({ ...defaultParameters, keySet: { ...defaultKeySet, secretKey: '' }, }); assert.equal(requestWithoutSecretKey.validate(), "Missing Secret Key"); }); it('should validate required token', () => { const requestWithoutToken = new RevokeTokenRequest({ ...defaultParameters, token: '', }); assert.equal(requestWithoutToken.validate(), "token can't be empty"); }); it('should pass validation with valid parameters', () => { const validRequest = new RevokeTokenRequest(defaultParameters); assert.equal(validRequest.validate(), undefined); }); it('should pass validation with only secret key and token', () => { const minimalRequest = new RevokeTokenRequest({ token: 'test_token', keySet: { subscribeKey: 'test_sub_key', secretKey: 'test_secret_key' }, }); assert.equal(minimalRequest.validate(), undefined); }); }); describe('operation', () => { it('should return PNAccessManagerRevokeToken operation', () => { const request = new RevokeTokenRequest(defaultParameters); assert.equal(request.operation(), RequestOperation.PNAccessManagerRevokeToken); }); }); describe('request method', () => { it('should use DELETE method', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); assert.equal(transportRequest.method, TransportMethod.DELETE); }); }); describe('URL construction', () => { it('should construct correct URL path with encoded token', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); const expectedPath = `/v3/pam/${defaultKeySet.subscribeKey}/grant/${encodeURIComponent(defaultParameters.token)}`; assert.equal(transportRequest.path, expectedPath); }); it('should construct URL with different subscribe key', () => { const customKeySet = { ...defaultKeySet, subscribeKey: 'custom_sub_key' }; const request = new RevokeTokenRequest({ ...defaultParameters, keySet: customKeySet, }); const transportRequest = request.request(); const expectedPath = `/v3/pam/custom_sub_key/grant/${encodeURIComponent(defaultParameters.token)}`; assert.equal(transportRequest.path, expectedPath); }); it('should handle different token values', () => { const customToken = 'qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQVDdXNyoENzcGOgRHV1aWShZnV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaGFubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoWpedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc'; const request = new RevokeTokenRequest({ ...defaultParameters, token: customToken, }); const transportRequest = request.request(); const expectedPath = `/v3/pam/${defaultKeySet.subscribeKey}/grant/${encodeURIComponent(customToken)}`; assert.equal(transportRequest.path, expectedPath); }); it('should handle tokens with special characters that need encoding', () => { const tokenWithSpecialChars = 'token+with/special=chars&more?data'; const request = new RevokeTokenRequest({ ...defaultParameters, token: tokenWithSpecialChars, }); const transportRequest = request.request(); const expectedPath = `/v3/pam/${defaultKeySet.subscribeKey}/grant/${encodeURIComponent(tokenWithSpecialChars)}`; assert.equal(transportRequest.path, expectedPath); // Verify that special characters are actually encoded using encodeString // encodeString does additional encoding beyond encodeURIComponent const encoded = transportRequest.path; assert(encoded.includes(encodeURIComponent(tokenWithSpecialChars))); }); it('should handle tokens with spaces', () => { const tokenWithSpaces = 'token with spaces'; const request = new RevokeTokenRequest({ ...defaultParameters, token: tokenWithSpaces, }); const transportRequest = request.request(); const expectedPath = `/v3/pam/${defaultKeySet.subscribeKey}/grant/${encodeURIComponent(tokenWithSpaces)}`; assert.equal(transportRequest.path, expectedPath); // Verify that spaces are encoded as %20 assert(transportRequest.path.includes('token%20with%20spaces')); }); it('should handle empty subscribe key', () => { const emptySubKeyRequest = new RevokeTokenRequest({ ...defaultParameters, keySet: { ...defaultKeySet, subscribeKey: '' }, }); const transportRequest = emptySubKeyRequest.request(); const expectedPath = `/v3/pam//grant/${encodeURIComponent(defaultParameters.token)}`; assert.equal(transportRequest.path, expectedPath); }); }); describe('token encoding edge cases', () => { it('should handle very long tokens', () => { const longToken = 'a'.repeat(1000); const request = new RevokeTokenRequest({ ...defaultParameters, token: longToken, }); const transportRequest = request.request(); assert(transportRequest.path.includes(encodeURIComponent(longToken))); }); it('should handle tokens with Unicode characters', () => { const unicodeToken = 'token_with_unicode_🚀_characters_ñ_ü'; const request = new RevokeTokenRequest({ ...defaultParameters, token: unicodeToken, }); const transportRequest = request.request(); assert(transportRequest.path.includes(encodeURIComponent(unicodeToken))); }); it('should handle tokens with Base64-like characters', () => { const base64Token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const request = new RevokeTokenRequest({ ...defaultParameters, token: base64Token, }); const transportRequest = request.request(); assert(transportRequest.path.includes(encodeURIComponent(base64Token))); }); it('should handle tokens with URL-unsafe characters', () => { const unsafeToken = 'token#with%unsafe&characters?and=more'; const request = new RevokeTokenRequest({ ...defaultParameters, token: unsafeToken, }); const transportRequest = request.request(); assert(transportRequest.path.includes(encodeURIComponent(unsafeToken))); }); }); describe('response parsing', () => { it('should parse successful revoke token response', async () => { const request = new RevokeTokenRequest(defaultParameters); const mockResponse: TransportResponse = { status: 200, url: 'https://test.pubnub.com', headers: { 'content-type': 'application/json' }, body: new TextEncoder().encode(JSON.stringify({ status: 200, data: {}, service: 'Access Manager', })), }; const parsedResponse = await request.parse(mockResponse); assert.deepEqual(parsedResponse, {}); }); it('should parse successful revoke response with additional data', async () => { const request = new RevokeTokenRequest(defaultParameters); const mockResponse: TransportResponse = { status: 200, url: 'https://test.pubnub.com', headers: { 'content-type': 'application/json' }, body: new TextEncoder().encode(JSON.stringify({ status: 200, data: { message: 'Token successfully revoked', timestamp: 1234567890, }, service: 'Access Manager', })), }; const parsedResponse = await request.parse(mockResponse); // Should always return empty object regardless of response content assert.deepEqual(parsedResponse, {}); }); it('should handle empty response body', async () => { const request = new RevokeTokenRequest(defaultParameters); const mockResponse: TransportResponse = { status: 200, url: 'https://test.pubnub.com', headers: { 'content-type': 'application/json' }, body: new TextEncoder().encode(''), }; try { await request.parse(mockResponse); // Should not reach here if parsing fails assert.fail('Expected parsing to fail with empty body'); } catch (error) { // Expected to throw due to invalid JSON assert(error instanceof Error); } }); it('should handle non-JSON response', async () => { const request = new RevokeTokenRequest(defaultParameters); const mockResponse: TransportResponse = { status: 200, url: 'https://test.pubnub.com', headers: { 'content-type': 'text/plain' }, body: new TextEncoder().encode('Success'), }; try { await request.parse(mockResponse); // Should not reach here if parsing fails assert.fail('Expected parsing to fail with non-JSON body'); } catch (error) { // Expected to throw due to invalid JSON assert(error instanceof Error); } }); }); describe('minimal configurations', () => { it('should work with only required parameters', () => { const minimalRequest = new RevokeTokenRequest({ token: 'test_token', keySet: { subscribeKey: 'test_sub_key', secretKey: 'test_secret' }, }); assert.equal(minimalRequest.validate(), undefined); assert.equal(minimalRequest.operation(), RequestOperation.PNAccessManagerRevokeToken); const transportRequest = minimalRequest.request(); assert.equal(transportRequest.method, TransportMethod.DELETE); assert(transportRequest.path.includes('test_token')); }); it('should not require publish key for validation', () => { const requestWithoutPubKey = new RevokeTokenRequest({ token: 'test_token', keySet: { subscribeKey: 'test_sub_key', secretKey: 'test_secret' }, }); assert.equal(requestWithoutPubKey.validate(), undefined); }); }); describe('edge cases', () => { it('should handle undefined keySet properties gracefully', () => { const request = new RevokeTokenRequest({ token: 'test_token', keySet: { secretKey: 'test_secret', subscribeKey: undefined as any, publishKey: undefined as any, }, }); const transportRequest = request.request(); assert.equal(transportRequest.path, `/v3/pam/undefined/grant/${encodeURIComponent('test_token')}`); }); it('should handle very short tokens', () => { const shortToken = 'a'; const request = new RevokeTokenRequest({ ...defaultParameters, token: shortToken, }); assert.equal(request.validate(), undefined); const transportRequest = request.request(); assert(transportRequest.path.includes(shortToken)); }); it('should handle numeric-like tokens', () => { const numericToken = '1234567890'; const request = new RevokeTokenRequest({ ...defaultParameters, token: numericToken, }); const transportRequest = request.request(); assert(transportRequest.path.includes(numericToken)); }); it('should handle tokens with only special characters', () => { const specialCharsToken = '!@#$%^&*()_+-=[]{}|;:,.<>?'; const request = new RevokeTokenRequest({ ...defaultParameters, token: specialCharsToken, }); const transportRequest = request.request(); // Verify the token is in the path (it will be encoded) assert(transportRequest.path.includes('/grant/')); }); }); describe('request properties', () => { it('should have empty query parameters object', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); // DELETE requests have empty query parameters object (not undefined) assert.deepEqual(transportRequest.queryParameters, {}); }); it('should not have request body', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); // DELETE requests typically don't have bodies assert.equal(transportRequest.body, undefined); }); it('should have default headers from AbstractRequest', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); // Should have default headers from AbstractRequest assert.deepEqual(transportRequest.headers, { 'Accept-Encoding': 'gzip, deflate' }); }); it('should be configured as non-compressible', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); // DELETE requests are typically not compressible assert.equal(transportRequest.compressible, false); }); }); describe('consistency with other access manager endpoints', () => { it('should follow same URL pattern as other v3 PAM endpoints', () => { const request = new RevokeTokenRequest(defaultParameters); const transportRequest = request.request(); // Should follow /v3/pam/{subscribeKey}/* pattern assert(transportRequest.path.startsWith('/v3/pam/')); assert(transportRequest.path.includes(defaultKeySet.subscribeKey!)); }); it('should require secret key like other PAM operations', () => { const requestWithoutSecret = new RevokeTokenRequest({ ...defaultParameters, keySet: { ...defaultKeySet, secretKey: '' }, }); // All PAM operations should require secret key assert.equal(requestWithoutSecret.validate(), "Missing Secret Key"); }); }); });