-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement the test for Collabora Online
Code was adapted from the OnlyOffice tests. Signed-off-by: Hubert Figuière <[email protected]>
- Loading branch information
Showing
10 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { Client } from './client' | ||
export { obtainDocumentInformation } from './info' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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('') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
packages/k6-tests/tests/koko/cool/010-open-change-save/baseline.k6.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
22 changes: 22 additions & 0 deletions
22
packages/k6-tests/tests/koko/cool/010-open-change-save/ramping.k6.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
133
packages/k6-tests/tests/koko/cool/010-open-change-save/simple.k6.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |