snapshot of https://github.com/ueberdosis/hocuspocus.git @ cf3cfa355b4d3bb61c6b6c80071f71783e32470e (2026-09-15)
Some checks failed
build / lint (22) (push) Failing after 17s
build / test (22) (push) Failing after 8s
build / lint (23) (push) Failing after 2s
build / build (22) (push) Has been skipped
build / build (23) (push) Has been skipped
build / test (23) (push) Failing after 4s
docsearch / docsearch (push) Failing after 1s
Some checks failed
build / lint (22) (push) Failing after 17s
build / test (22) (push) Failing after 8s
build / lint (23) (push) Failing after 2s
build / build (22) (push) Has been skipped
build / build (23) (push) Has been skipped
build / test (23) (push) Failing after 4s
docsearch / docsearch (push) Failing after 1s
This commit is contained in:
365
tests/server/onTokenSync.ts
Normal file
365
tests/server/onTokenSync.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
import test from 'ava'
|
||||
import type { onAuthenticatePayload, onTokenSyncPayload } from '@hocuspocus/server'
|
||||
import { Unauthorized } from '@hocuspocus/common'
|
||||
import {
|
||||
newHocuspocus, newHocuspocusProvider, newHocuspocusProviderWebsocket, sleep,
|
||||
} from '../utils/index.ts'
|
||||
|
||||
// ============================================================================
|
||||
// PROVIDER SEND TOKEN TESTS
|
||||
// Provider calls sendToken() after onAuthenticated()
|
||||
// ============================================================================
|
||||
|
||||
test('provider sendToken: onTokenSync receives correct token from provider after authentication', async t => {
|
||||
await new Promise(async resolve => {
|
||||
const expectedToken = 'UPDATED-TOKEN'
|
||||
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true // Allow initial auth
|
||||
},
|
||||
async onTokenSync({ token }: onTokenSyncPayload) {
|
||||
t.is(token, expectedToken)
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'INITIAL-TOKEN', // Initial token for auth
|
||||
onAuthenticated() {
|
||||
// Update the provider's token before sending
|
||||
provider.configuration.token = expectedToken
|
||||
// Provider sends updated token after authentication
|
||||
provider.sendToken() // This will send the current token
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('provider sendToken: executes onTokenSync from custom extension when provider sends token', async t => {
|
||||
await new Promise(async resolve => {
|
||||
class CustomExtension {
|
||||
async onAuthenticate() {
|
||||
return true
|
||||
}
|
||||
|
||||
async onTokenSync({ token }: onTokenSyncPayload) {
|
||||
t.is(token, 'SUPER-SECRET-TOKEN')
|
||||
t.pass()
|
||||
resolve('done')
|
||||
}
|
||||
}
|
||||
|
||||
const server = await newHocuspocus(t, {
|
||||
extensions: [
|
||||
new CustomExtension(),
|
||||
],
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'SUPER-SECRET-TOKEN',
|
||||
onAuthenticated() {
|
||||
provider.sendToken()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('provider sendToken: onTokenSync has access to full payload when provider sends token', async t => {
|
||||
const mockContext = { user: 123 }
|
||||
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return mockContext
|
||||
},
|
||||
async onTokenSync({ context, connection, documentName, token }: onTokenSyncPayload) {
|
||||
t.deepEqual(context, mockContext)
|
||||
t.truthy(connection)
|
||||
t.is(documentName, 'hocuspocus-test')
|
||||
t.is(token, 'SUPER-SECRET-TOKEN')
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'SUPER-SECRET-TOKEN',
|
||||
onAuthenticated() {
|
||||
provider.sendToken()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('provider sendToken: onTokenSync works with multiple providers sending tokens', async t => {
|
||||
const doc1 = 'document1'
|
||||
const doc2 = 'document2'
|
||||
const token1 = 'TOKEN-1'
|
||||
const token2 = 'TOKEN-2'
|
||||
let completedCount = 0
|
||||
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true // Allow initial auth
|
||||
},
|
||||
async onTokenSync({ documentName, token }: onTokenSyncPayload) {
|
||||
// Direct verification based on document name
|
||||
if (documentName === doc1) {
|
||||
t.is(token, token1)
|
||||
} else if (documentName === doc2) {
|
||||
t.is(token, token2)
|
||||
}
|
||||
|
||||
completedCount++
|
||||
if (completedCount === 2) {
|
||||
resolve('done')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const socket = newHocuspocusProviderWebsocket(t, server)
|
||||
|
||||
// Create two providers for different documents
|
||||
const provider1 = newHocuspocusProvider(t, server, {
|
||||
websocketProvider: socket,
|
||||
token: token1,
|
||||
name: doc1,
|
||||
onAuthenticated() {
|
||||
provider1.sendToken()
|
||||
},
|
||||
})
|
||||
|
||||
const provider2 = newHocuspocusProvider(t, server, {
|
||||
websocketProvider: socket,
|
||||
token: token2,
|
||||
name: doc2,
|
||||
onAuthenticated() {
|
||||
provider2.sendToken()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SERVER REQUEST TOKEN TESTS
|
||||
// Server calls connection.requestToken() after onAuthenticate() completes
|
||||
// ============================================================================
|
||||
|
||||
test('server requestToken: executes onTokenSync when server requests token after authentication', async t => {
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true
|
||||
},
|
||||
async onTokenSync({ token }: onTokenSyncPayload) {
|
||||
t.is(token, 'SUPER-SECRET-TOKEN')
|
||||
t.pass()
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'SUPER-SECRET-TOKEN',
|
||||
})
|
||||
|
||||
// Wait for provider to be authenticated to be able to find the connection
|
||||
await sleep(100)
|
||||
|
||||
const document = server.documents.get('hocuspocus-test')
|
||||
if (document) {
|
||||
const connection = document.connections.keys().next().value
|
||||
if (connection) {
|
||||
connection.requestToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('server requestToken: executes onTokenSync from custom extension when server requests token', async t => {
|
||||
await new Promise(async resolve => {
|
||||
class CustomExtension {
|
||||
async onAuthenticate() {
|
||||
return true
|
||||
}
|
||||
|
||||
async onTokenSync({ token }: onTokenSyncPayload) {
|
||||
t.is(token, 'SUPER-SECRET-TOKEN')
|
||||
t.pass()
|
||||
resolve('done')
|
||||
}
|
||||
}
|
||||
|
||||
const server = await newHocuspocus(t, {
|
||||
extensions: [
|
||||
new CustomExtension(),
|
||||
],
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'SUPER-SECRET-TOKEN',
|
||||
})
|
||||
|
||||
await sleep(100)
|
||||
|
||||
const document = server.documents.get('hocuspocus-test')
|
||||
if (document) {
|
||||
const connection = document.connections.keys().next().value
|
||||
if (connection) {
|
||||
connection.requestToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('server requestToken: onTokenSync receives correct token when server requests it', async t => {
|
||||
await new Promise(async resolve => {
|
||||
const expectedToken = 'UPDATED-TOKEN'
|
||||
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true
|
||||
},
|
||||
async onTokenSync({ token }: onTokenSyncPayload) {
|
||||
t.is(token, expectedToken)
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'INITIAL-TOKEN',
|
||||
onAuthenticated() {
|
||||
// Update the provider's token after connection
|
||||
provider.configuration.token = expectedToken
|
||||
},
|
||||
})
|
||||
|
||||
await sleep(100)
|
||||
|
||||
// Now trigger token sync request from server
|
||||
const document = server.documents.get('hocuspocus-test')
|
||||
if (document) {
|
||||
const connection = document.connections.keys().next().value
|
||||
if (connection) {
|
||||
connection.requestToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('server requestToken: onTokenSync works with multiple documents when server requests tokens', async t => {
|
||||
const doc1 = 'document1'
|
||||
const doc2 = 'document2'
|
||||
const token1 = 'TOKEN-1'
|
||||
const token2 = 'TOKEN-2'
|
||||
let completedCount = 0
|
||||
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true // Allow initial auth
|
||||
},
|
||||
async onTokenSync({ documentName, token }: onTokenSyncPayload) {
|
||||
// Direct verification based on document name
|
||||
if (documentName === doc1) {
|
||||
t.is(token, token1)
|
||||
} else if (documentName === doc2) {
|
||||
t.is(token, token2)
|
||||
}
|
||||
|
||||
completedCount++
|
||||
if (completedCount === 2) {
|
||||
resolve('done')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const socket = newHocuspocusProviderWebsocket(t, server)
|
||||
|
||||
// Create two providers for different documents
|
||||
const provider1 = newHocuspocusProvider(t, server, {
|
||||
websocketProvider: socket,
|
||||
token: token1,
|
||||
name: doc1,
|
||||
})
|
||||
|
||||
const provider2 = newHocuspocusProvider(t, server, {
|
||||
websocketProvider: socket,
|
||||
token: token2,
|
||||
name: doc2,
|
||||
})
|
||||
|
||||
// Wait for both authentications to complete
|
||||
await sleep(100)
|
||||
|
||||
// Now trigger token sync requests from server for both documents
|
||||
const doc1Connection = server.documents.get(doc1)?.connections.keys().next().value
|
||||
const doc2Connection = server.documents.get(doc2)?.connections.keys().next().value
|
||||
|
||||
if (doc1Connection) doc1Connection.requestToken()
|
||||
if (doc2Connection) doc2Connection.requestToken()
|
||||
})
|
||||
})
|
||||
|
||||
test('server requestToken: onTokenSync works with readonly connections when server requests token', async t => {
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate({ connectionConfig }: onAuthenticatePayload) {
|
||||
connectionConfig.readOnly = true
|
||||
return true
|
||||
},
|
||||
async onTokenSync({ connection }: onTokenSyncPayload) {
|
||||
t.is(connection.readOnly, true)
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'readonly-token',
|
||||
})
|
||||
|
||||
// Wait for authentication to complete first
|
||||
await sleep(100)
|
||||
|
||||
// Now trigger token sync request from server
|
||||
const document = server.documents.get('hocuspocus-test')
|
||||
if (document) {
|
||||
const connection = document.connections.keys().next().value
|
||||
if (connection) {
|
||||
connection.requestToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('server requestToken: failure of onTokenSync should close the connection', async t => {
|
||||
await new Promise(async resolve => {
|
||||
const server = await newHocuspocus(t, {
|
||||
async onAuthenticate() {
|
||||
return true // Allow initial auth
|
||||
},
|
||||
async onTokenSync() {
|
||||
throw new Error()
|
||||
},
|
||||
})
|
||||
|
||||
const provider = newHocuspocusProvider(t, server, {
|
||||
token: 'SUPER-SECRET-TOKEN',
|
||||
onClose({ event }) {
|
||||
t.is(event.reason, Unauthorized.reason)
|
||||
t.pass()
|
||||
resolve('done')
|
||||
},
|
||||
})
|
||||
|
||||
await sleep(100)
|
||||
|
||||
const document = server.documents.get('hocuspocus-test')
|
||||
if (document) {
|
||||
const connection = document.connections.keys().next().value
|
||||
if (connection) {
|
||||
connection.requestToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user