Skip to content

Commit

Permalink
Added tests for access manager. (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcin-cebo authored Jan 21, 2025
1 parent e55b101 commit 672393d
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 29 deletions.
106 changes: 106 additions & 0 deletions js-chat/tests/access-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
Chat
} from "../dist-test"
import {
createChatInstance,
makeid,
sleep
} from "./utils";

import { jest } from "@jest/globals"

describe("Access Manager test test", () => {
jest.retryTimes(2)

let chatPamClient: Chat
let chatPamServer: Chat

beforeAll(async () => {
let chatClientUserId = makeid(8)
chatPamServer = await createChatInstance( { shouldCreateNewInstance: true, clientType: 'PamServer' })
const token = await _grantTokenForUserId(chatPamServer, chatClientUserId);
chatPamClient = await createChatInstance( {
userId: chatClientUserId ,
shouldCreateNewInstance: true,
clientType: 'PamClient',
config: {
authKey: token
},
})
})

afterEach(async () => {
jest.clearAllMocks()
})

// test is skipped because it has 65sec sleep to wait for token expiration
test.skip("when token is updated then client can use API", async () => {
const user1Id = `user1_${Date.now()}`
const userToChatWith = await chatPamServer.createUser(user1Id, { name: "User1" })
const createDirectConversationResult = await chatPamServer.createDirectConversation(
{
user: userToChatWith,
channelData: {
name: "Quick sync on customer XYZ"
},
membershipData: {
custom: {
purpose: "premium-support"
}
}
}
)
let channelId = createDirectConversationResult.channel.id
let token = await _grantTokenForChannel(1, chatPamServer, channelId);
await chatPamClient.sdk.setToken(token)

const channelRetrievedByClient = await chatPamClient.getChannel(createDirectConversationResult.channel.id);
expect(channelRetrievedByClient).toBeDefined();

// Verify that the fetched channel ID matches the expected channel ID
expect(channelRetrievedByClient?.id).toEqual(channelId);

let publishResult = await channelRetrievedByClient.sendText("my first message");
let message = await channelRetrievedByClient.getMessage(publishResult.timetoken);
await message.toggleReaction("one")

// sleep so that token expires
await sleep(65000)
token = await _grantTokenForChannel(1, chatPamServer, channelId);
await chatPamClient.sdk.setToken(token)

await message.toggleReaction("two");
await chatPamClient.getChannel(channelRetrievedByClient?.id);

await chatPamServer.deleteChannel(channelId)
}, 100000); // this long timeout is needed so we can wait 65sec for token to expire

async function _grantTokenForUserId(chatPamServer, chatClientUserId) {
return chatPamServer.sdk.grantToken({
ttl: 10,
resources: {
uuids: {
[chatClientUserId]: {
get: true,
update: true
}
}
}
});
}

async function _grantTokenForChannel(ttl, chatPamServer, channelId) {
return chatPamServer.sdk.grantToken({
ttl: ttl,
resources: {
channels: {
[channelId]: {
read: true,
write: true,
get: true, // this is important
}
}
}
});
}
})
86 changes: 63 additions & 23 deletions js-chat/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,83 @@ export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

export async function createChatInstance(
options: {
userId?: string
shouldCreateNewInstance?: boolean
config?: Partial<ChatConfig> & PubNub.PubnubConfig
} = {}
) {
type ClientType = 'PamClient' | 'PamServer' | 'NoPam';

const createChat = async (
userId: string,
config?: Partial<ChatConfig> & PubNub.PubnubConfig,
clientType?: ClientType,

): Promise<Chat> => {

const keysetError = `
#######################################################
# Could not read the PubNub keyset from the .env file #
#######################################################
`

if (!process.env.PUBLISH_KEY || !process.env.SUBSCRIBE_KEY || !process.env.USER_ID)
// Determine keys based on clientType
let publishKey: string | undefined;
let subscribeKey: string | undefined;
let secretKey: string | undefined;

switch (clientType) {
case 'PamClient':
publishKey = process.env.PAM_PUBLISH_KEY;
subscribeKey = process.env.PAM_SUBSCRIBE_KEY;
break;

case 'PamServer':
publishKey = process.env.PAM_PUBLISH_KEY;
subscribeKey = process.env.PAM_SUBSCRIBE_KEY;
secretKey = process.env.PAM_SECRET_KEY;
break;

case 'NoPam':
default:
publishKey = process.env.PUBLISH_KEY;
subscribeKey = process.env.SUBSCRIBE_KEY;
break;
}

// Validate required keys
if (!publishKey || !subscribeKey || (clientType === 'PamServer' && !secretKey)) {
throw keysetError
}
// Build the chat configuration
const chatConfig: Partial<ChatConfig> & PubNub.PubnubConfig = {
publishKey,
subscribeKey,
userId,
...config,
};

// Include secretKey only if clientType is 'PamServer'
if (clientType === 'PamServer' && secretKey) {
chatConfig.secretKey = secretKey;
}

return Chat.init(chatConfig);
};

export async function createChatInstance(
options: {
userId?: string
shouldCreateNewInstance?: boolean
config?: Partial<ChatConfig> & PubNub.PubnubConfig
clientType?: ClientType
} = {}
) {

if (options.shouldCreateNewInstance) {
return await Chat.init({
publishKey: process.env.PUBLISH_KEY,
subscribeKey: process.env.SUBSCRIBE_KEY,
userId: options.userId || process.env.USER_ID,
// logVerbosity: true,
...options.config,
})
return await createChat(options.userId || process.env.USER_ID!, options.config, options.clientType);
}

if (!chat) {
chat = await Chat.init({
publishKey: process.env.PUBLISH_KEY,
subscribeKey: process.env.SUBSCRIBE_KEY,
userId: options.userId || process.env.USER_ID,
// logVerbosity: true,
...options.config,
})
chat = await createChat(options.userId || process.env.USER_ID!, options.config, options.clientType);
}

return chat
return chat;
}

export function createRandomChannel(prefix?: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package com.pubnub.integration
import com.pubnub.api.PubNubException
import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant
import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant
import com.pubnub.chat.Chat
import com.pubnub.chat.Event
import com.pubnub.chat.internal.message.MessageImpl
import com.pubnub.chat.listenForEvents
import com.pubnub.chat.types.EventContent
import com.pubnub.internal.PLATFORM
import com.pubnub.test.await
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.time.Duration.Companion.minutes

class AccessManagerTest : BaseChatIntegrationTest() {
@Test
Expand All @@ -34,7 +37,15 @@ class AccessManagerTest : BaseChatIntegrationTest() {
chatPamServer.createChannel(id = channelId).await()
val token = chatPamServer.pubNub.grantToken(
ttl = 1,
channels = listOf(ChannelGrant.name(get = true, name = channelId, read = true, write = true, manage = true)) // get = true
channels = listOf(
ChannelGrant.name(
get = true,
name = channelId,
read = true,
write = true,
manage = true
)
) // get = true
).await().token
// client uses token generated by server
chatPamClient.pubNub.setToken(token)
Expand All @@ -47,6 +58,37 @@ class AccessManagerTest : BaseChatIntegrationTest() {
chatPamServer.deleteChannel(id = channelId).await()
}

@Ignore // this test has 65 sec delay to wait for token to expire. To run it on JS extend timeout in Mocha to 70s.
@Test
fun `when token is updated then client can use API`() = runTest(timeout = 2.minutes) {
if (PLATFORM == "iOS") {
return@runTest
}
// getToken from server
val channelId = channelPam.id
chatPamServer.createChannel(id = channelId).await()
// todo extract to the function ?
val token = generateToken(chatPamServer, channelId, 1)
chatPamClient.pubNub.setToken(token)

val channel = chatPamClient.getChannel(channelId).await()!!
val actualChannelId = channel.id
assertEquals(channelId, actualChannelId)

val publishResult = channel.sendText("my first message").await()
val message = channel.getMessage(publishResult.timetoken).await()!!
message.toggleReaction("one").await()

delayInMillis(65000)
val token2 = generateToken(chatPamServer, channelId, 1)
chatPamClient.pubNub.setToken(token2)

message.toggleReaction("three").await()
chatPamClient.getChannel(channelId).await()?.id

chatPamServer.deleteChannel(id = channelId).await()
}

@Test
fun setLastReadMessageTimetoken_should_send_Receipt_event_when_has_token() = runTest {
if (PLATFORM == "iOS") {
Expand Down Expand Up @@ -100,4 +142,11 @@ class AccessManagerTest : BaseChatIntegrationTest() {

chatPamServer.deleteChannel(channelId).await()
}

private suspend fun generateToken(chat: Chat, channelId: String, ttl: Int): String {
return chat.pubNub.grantToken(
ttl = ttl,
channels = listOf(ChannelGrant.name(get = true, name = channelId, read = true, write = true, manage = true))
).await().token
}
}
Loading

0 comments on commit 672393d

Please sign in to comment.