Skip to content

Commit

Permalink
Implement the test for Collabora Online
Browse files Browse the repository at this point in the history
Code was adapted from the OnlyOffice tests.

Signed-off-by: Hubert Figuière <[email protected]>
  • Loading branch information
hfiguiere committed Jul 9, 2024
1 parent 706bb5a commit 861f97e
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 0 deletions.
88 changes: 88 additions & 0 deletions packages/k6-tests/src/clients/cool/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { sleep } from 'k6'
import { ErrorEvent } from 'k6/experimental/websockets'

import { FileInfo, UserAuth } from './info'
import { EngineType, MessageType, Session, SocketType } from './io'
import { docsWorker } from './worker'

export class Client {
// @ts-ignore
private session: Session

private token: string

private documentId: string

private userAuth: UserAuth

private fileInfo: FileInfo

private wopiSrc: string

constructor(p: { url: string, access_token: string, documentId: string, userAuth: UserAuth, fileInfo: FileInfo }) {

// eslint-disable-next-line @typescript-eslint/naming-convention
const { access_token, access_token_ttl } = p

const appUrl = new URL(p.url)
const wopiSrc = app_url.searchParams.get('WOPISrc')

const wopiUrl = new URL(wopiSrc)
wopiUrl.searchParams.set('access_token', access_token)
wopiUrl.searchParams.set('access_token_ttl', access_token_ttl)

const wssUrl = new URL(`wss://${appUrl.hostname}:${app_url.port}/cool/${encodeURIComponent(wopiUrl)}/ws`)
wssUrl.searchParams.set('WOPISrc', wopiSrc)
wssUrl.searchParams.set('compat', '/ws')

this.token = p.token
this.documentId = p.documentId
this.userAuth = p.userAuth
this.fileInfo = p.fileInfo
this.wopiSrc = wopiSrc
this.session = new Session({ url: wss_url })
}

onError(h: (event?: ErrorEvent) => void) {
this.session.onError(h)
}

async establishSession(): Promise<void> {
await docsWorker({ session: this.session })
await this.session.waitFor({ engineType: EngineType.enum.open })
await this.session.publish({ data: `load url=${this.wopiSrc} accessibilityState=false` +
' deviceFormFactor=desktop darkTheme=false timezone=America/Montreal' })
await this.session.waitFor({ engineType: EngineType.enum.message, socketType: SocketType.enum.connect })
}

async makeChanges(p: { changes: Array<string> }) {
await this.session.publish({ data: isSaveLockMessage() })
const { messageData: { saveLock: isLocked } } = await this.session.waitFor({
engineType: EngineType.enum.message,
socketType: SocketType.enum.event,
messageType: MessageType.enum.saveLock
})

if (isLocked) {
sleep(0.5)
await this.makeChanges(p)
return
}

p.changes.forEach(async (change) => {
await this.session.publish({ data: change })
})

await this.session.publish({ data: 'save dontTerminateEdit=0 dontSaveIfUnmodified=0' })

await this.session.waitFor({
engineType: EngineType.enum.message,
socketType: SocketType.enum.event,
messageType: MessageType.enum.unSaveLock
})
}

disconnect() {
this.session.disconnect()
}
}
2 changes: 2 additions & 0 deletions packages/k6-tests/src/clients/cool/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Client } from './client'
export { obtainDocumentInformation } from './info'
67 changes: 67 additions & 0 deletions packages/k6-tests/src/clients/cool/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Client } from '@ownclouders/k6-tdk/lib/client'
import { objectToQueryString } from '@ownclouders/k6-tdk/lib/utils'
import { z } from 'zod'

const UserAuth = z.object({
wopiSrc: z.string(),
access_token: z.string(),
access_token_ttl: z.number(),
userSessionId: z.string(),
mode: z.string()
})
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type UserAuth = z.infer<typeof UserAuth>

const FileInfo = z.object({
BaseFileName: z.string(),
BreadcrumbDocName: z.string(),
HostEditUrl: z.string(),
HostViewUrl: z.string(),
BreadcrumbFolderUrl: z.string(),
IsAnonymousUser: z.boolean(),
UserFriendlyName: z.string(),
BreadcrumbFolderName: z.string(),
DownloadUrl: z.string(),
FileUrl: z.string(),
BreadcrumbBrandName: z.string(),
BreadcrumbBrandUrl: z.string(),
OwnerId: z.string(),
UserId: z.string(),
Size: z.number(),
Version: z.string(),
SupportsExtendedLockLength: z.boolean(),
SupportsGetLock: z.boolean(),
SupportsUpdate: z.boolean(),
UserCanWrite: z.boolean(),
SupportsLocks: z.boolean(),
SupportsDeleteFile: z.boolean(),
UserCanNotWriteRelative: z.boolean(),
SupportsRename: z.boolean(),
UserCanRename: z.boolean(),
SupportsContainers: z.boolean(),
SupportsUserInfo: z.boolean()
})
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type FileInfo = z.infer<typeof FileInfo>

export const obtainDocumentInformation = async (p: {
client: Client,
resourceId: string,
appName: string
}) => {
const openRequestParams = { file_id: p.resourceId, lang: 'de', app_name: p.appName }
const appOpenResponse = p.client.httpClient<'text'>(
'POST',
`/app/open?${objectToQueryString(openRequestParams)}`,
JSON.stringify(openRequestParams)
)

// eslint-disable-next-line @typescript-eslint/naming-convention
const { app_url, form_parameters: { access_token, access_token_ttl } } = JSON.parse(appOpenResponse.body)

return {
app_url,
access_token,
access_token_ttl
}
}
2 changes: 2 additions & 0 deletions packages/k6-tests/src/clients/cool/io/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { EngineType, MessageType, SocketType } from '../../onlyoffice/io/io'
export { Session } from '../../onlyoffice/io/session'
9 changes: 9 additions & 0 deletions packages/k6-tests/src/clients/cool/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { EngineType, SocketType } from './io'

export const encodeData = (p: { engineType: EngineType, socketType?: SocketType, data?: unknown }) => {
return [p.engineType, p.socketType, JSON.stringify(p.data)]
.filter((v) => {
return v !== undefined
})
.join('')
}
22 changes: 22 additions & 0 deletions packages/k6-tests/src/clients/cool/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { EngineType, MessageType, Session, SocketType } from './io'

export const docsWorker = async (p: { session: Session }): Promise<void> => {

p.session.subscribe({
engineType: EngineType.enum.message,
socketType: SocketType.enum.event,
messageType: MessageType.enum.error,
fn: async ({ messageData }) => {
console.error(messageData)
}
})

p.session.subscribe({
engineType: EngineType.enum.message,
socketType: SocketType.enum.event,
messageType: MessageType.enum.drop,
fn: async ({ messageData }) => {
console.error(messageData)
}
})
}
8 changes: 8 additions & 0 deletions packages/k6-tests/src/values/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ export const envValues = () => {
get app_name() {
return ENV('ONLY_OFFICE_APP_NAME', 'OnlyOffice')
}
},
cool: {
get wss_url() {
return ENV('COOL_WSS_URL', 'wss://localhost:9980/')
},
get app_name() {
return ENV('COOL_APP_NAME', 'Collabora')
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Options } from 'k6/options'

import { open_change_save_010, open_change_save_010_setup, open_change_save_010_teardown, options as inherited_options } from './simple.k6'

export const options: Options = {
...inherited_options,
iterations: 10,
duration: '7d',
teardownTimeout: '1h'
}

export const setup = open_change_save_010_setup
export default open_change_save_010
export const teardown = open_change_save_010_teardown
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Options } from 'k6/options'
import { omit } from 'lodash'

import { options as inherited_options } from './baseline.k6'

export { open_change_save_010, open_change_save_010_setup as setup, open_change_save_010_teardown as teardown } from './simple.k6'

export const options: Options = {
...omit(inherited_options, 'iterations', 'duration'),
scenarios: {
open_change_save_010: {
executor: 'ramping-vus',
startVUs: 0,
exec: 'open_change_save_010',
stages: [
{ target: 10, duration: '20s' },
{ target: 10, duration: '30s' },
{ target: 0, duration: '10s' }
]
}
}
}
133 changes: 133 additions & 0 deletions packages/k6-tests/tests/koko/cool/010-open-change-save/simple.k6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ENV, queryXml, store } from '@ownclouders/k6-tdk/lib/utils'
import { sleep } from 'k6'
import exec from 'k6/execution'
import { Counter } from 'k6/metrics'
import { Options } from 'k6/options'

import { Client, obtainDocumentInformation } from '@/clients/cool'
import { userPool } from '@/pools'
import { clientFor } from '@/shortcuts'
import { getTestRoot } from '@/test'
import { getPoolItem } from '@/utils'
import { envValues } from '@/values'

export interface Environment {
testRoot: string;
}

// eslint-disable-next-line no-restricted-globals
const docX = open('../data/sample.docx', 'b')

export const options: Options = {
vus: 1,
iterations: 1,
insecureSkipTLSVerify: true
}

const settings = {
...envValues(),
get docx() {
return ENV('DOCX', [settings.seed.resource.root, 'sample.docx'].join('/'))
}
}

const coolClientErrors = new Counter('cool_client_errors')

export const open_change_save_010_setup = async (): Promise<Environment> => {
const adminClient = clientFor({ userLogin: settings.admin.login, userPassword: settings.admin.password })
const rootInfo = await getTestRoot({
client: adminClient,
userLogin: settings.admin.login,
platform: settings.platform.type,
resourceName: settings.seed.container.name,
resourceType: settings.seed.container.type,
isOwner: false
})

const testRoot = [rootInfo.root, rootInfo.path].join('/')

adminClient.resource.uploadResource({
root: testRoot,
resourcePath: settings.docx,
resourceBytes: docX
})

return {
testRoot
}
}

export const open_change_save_010 = async ({ testRoot }: Environment): Promise<void> => {
const user = getPoolItem({ pool: userPool, n: exec.vu.idInTest })
const userStore = store(user.userLogin)
const documentInformation = await userStore.setOrGet('root', async () => {
const ocisClient = clientFor(user)

const getResourcePropertiesResponse = await ocisClient.resource.getResourceProperties({
root: testRoot,
resourcePath: settings.docx
})
sleep(settings.sleep.after_request)

const [resourceId] = queryXml("$..['oc:fileid']", getResourcePropertiesResponse.body)
return obtainDocumentInformation({ client: ocisClient, appName: settings.cool.app_name, resourceId })
})

const { app_url } = documentInformation

const coolClient = new Client({
...documentInformation,
url: app_url
})

coolClient.onError((err) => {
console.error(err?.error)
coolClientErrors.add(1, { errorType: err?.error || 'unknown' })
})

await coolClient.establishSession()
sleep(settings.sleep.after_request)

// todo: move into pool
const changes = [
'textinput id=0 text=H',
'textinput id=0 text=e',
'textinput id=0 text=l',
'textinput id=0 text=l',
'textinput id=0 text=o',
'key type=input char=32 key=0',
'textinput id=0 text=w',
'textinput id=0 text=o',
'textinput id=0 text=r',
'textinput id=0 text=l',
'textinput id=0 text=d',
'key type=input char=33 key=0'
]

await coolClient.makeChanges({ changes })
sleep(settings.sleep.after_request)

coolClient.disconnect()
sleep(settings.sleep.after_iteration)
}

export const open_change_save_010_teardown = ({ testRoot }: Environment): void => {
const adminClient = clientFor({ userLogin: settings.admin.login, userPassword: settings.admin.password })

const waitForUnlock = () => {
const { body } = adminClient.resource.getResourceProperties({ root: testRoot, resourcePath: settings.docx })

if(queryXml("$..['d:activelock']", body).length !== 0){
sleep(1)
waitForUnlock()
}
}

waitForUnlock()

adminClient.resource.deleteResource({ root: testRoot, resourcePath: settings.docx })
}

export const setup = open_change_save_010_setup
export default open_change_save_010
export const teardown = open_change_save_010_teardown

0 comments on commit 861f97e

Please sign in to comment.