enso/app/gui2/shared/dataServer/mock.ts
somebody1234 9b7e3d0f16
E2E tests (#8239)
- Closes #8179

# Important Notes
- ⚠️ These tests are currently *not run* on any CI workflow.
- There is some unused code for mocking the PM. This has been intentionally kept, as this may be useful in the future.
Note that this may be useful for testing the dashboard, however the dashboard is currently only tested in cloud mode
- that is, without the backend switcher, and with only the remote backend available. As such, currently it uses HTTP API mocks, and no PM mock.
2023-11-27 15:48:37 +00:00

208 lines
6.9 KiB
TypeScript

import { createSHA3 } from 'hash-wasm'
import * as random from 'lib0/random'
import {
Builder,
ByteBuffer,
ChecksumBytesCommand,
ChecksumBytesReply,
EnsoDigest,
EnsoUUID,
ErrorPayload,
Error as ErrorResponse,
FileContentsReply,
InboundMessage,
InboundPayload,
InitSessionCommand,
None,
Null,
OutboundMessage,
OutboundPayload,
Path,
ReadBytesCommand,
ReadBytesReply,
ReadFileCommand,
Success,
WriteBytesCommand,
WriteFileCommand,
type AnyOutboundPayload,
type Offset,
type Table,
} from '../binaryProtocol'
import { LanguageServerErrorCode } from '../languageServerTypes'
import { uuidToBits } from '../uuid'
const sha3 = createSHA3(224)
function pathSegments(path: Path) {
return Array.from({ length: path.segmentsLength() }, (_, i) => path.segments(i))
}
function createError(builder: Builder, code: LanguageServerErrorCode, message: string) {
const messageOffset = builder.createString(message)
return {
type: OutboundPayload.ERROR,
offset: ErrorResponse.createError(builder, code, messageOffset, ErrorPayload.NONE, Null),
}
}
function createMessageId(builder: Builder) {
const messageUuid = random.uuidv4()
const [leastSigBits, mostSigBits] = uuidToBits(messageUuid)
return EnsoUUID.createEnsoUUID(builder, leastSigBits, mostSigBits)
}
function createCorrelationId(messageId: EnsoUUID) {
return (builder: Builder) =>
EnsoUUID.createEnsoUUID(builder, messageId.leastSigBits(), messageId.mostSigBits())
}
const PAYLOAD_CONSTRUCTOR = {
[InboundPayload.NONE]: None,
[InboundPayload.INIT_SESSION_CMD]: InitSessionCommand,
[InboundPayload.WRITE_FILE_CMD]: WriteFileCommand,
[InboundPayload.READ_FILE_CMD]: ReadFileCommand,
[InboundPayload.WRITE_BYTES_CMD]: WriteBytesCommand,
[InboundPayload.READ_BYTES_CMD]: ReadBytesCommand,
[InboundPayload.CHECKSUM_BYTES_CMD]: ChecksumBytesCommand,
} satisfies Record<InboundPayload, new () => Table>
export function mockDataWSHandler(
readFile: (segments: string[]) => Promise<ArrayBuffer | null | undefined>,
cb?: (send: (data: string | Blob | ArrayBufferLike | ArrayBufferView) => void) => void,
) {
let sentSend = false
return async (
message: string | Blob | ArrayBufferLike | ArrayBufferView,
send: (data: string | Blob | ArrayBufferLike | ArrayBufferView) => void,
) => {
if (!sentSend) cb?.(send)
sentSend = true
if (!(message instanceof ArrayBuffer)) return
const binaryMessage = InboundMessage.getRootAsInboundMessage(new ByteBuffer(message))
const payloadType = binaryMessage.payloadType()
const payload = binaryMessage.payload(new PAYLOAD_CONSTRUCTOR[payloadType]())
if (!payload) return
const builder = new Builder()
let response: { type: OutboundPayload; offset: Offset<AnyOutboundPayload> } | undefined
switch (payloadType) {
case InboundPayload.NONE: {
response = {
type: OutboundPayload.NONE,
offset: Null,
}
break
}
case InboundPayload.INIT_SESSION_CMD: {
response = {
type: OutboundPayload.SUCCESS,
offset: Success.createSuccess(builder),
}
break
}
case InboundPayload.WRITE_FILE_CMD: {
response = createError(
builder,
LanguageServerErrorCode.AccessDenied,
'Cannot WriteFile to a read-only mock.',
)
break
}
case InboundPayload.READ_FILE_CMD: {
const payload_ = payload as ReadFileCommand
const path = payload_.path()
if (!path) {
response = createError(builder, LanguageServerErrorCode.NotFile, 'Invalid Path')
break
}
const file = await readFile(pathSegments(path))
if (!file) {
response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found')
break
}
const contentOffset = builder.createString(file)
response = {
type: OutboundPayload.FILE_CONTENTS_REPLY,
offset: FileContentsReply.createFileContentsReply(builder, contentOffset),
}
break
}
case InboundPayload.WRITE_BYTES_CMD: {
response = createError(
builder,
LanguageServerErrorCode.AccessDenied,
'Cannot WriteBytes to a read-only mock.',
)
break
}
case InboundPayload.READ_BYTES_CMD: {
const payload_ = payload as ReadBytesCommand
const segment = payload_.segment()
const path = segment && segment.path()
if (!path) {
response = createError(
builder,
LanguageServerErrorCode.NotFile,
'Invalid FileSegment or Path',
)
break
}
const file = await readFile(pathSegments(path))
if (!file) {
response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found')
break
}
const start = Number(segment.byteOffset())
const slice = file.slice(start, start + Number(segment.length()))
const contentOffset = builder.createString(slice)
const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary')
const checksumBytesOffset = EnsoDigest.createBytesVector(builder, digest)
const checksumOffset = EnsoDigest.createEnsoDigest(builder, checksumBytesOffset)
response = {
type: OutboundPayload.READ_BYTES_REPLY,
offset: ReadBytesReply.createReadBytesReply(builder, checksumOffset, contentOffset),
}
break
}
case InboundPayload.CHECKSUM_BYTES_CMD: {
const payload_ = payload as ChecksumBytesCommand
const segment = payload_.segment()
const path = segment && segment.path()
if (!path) {
response = createError(
builder,
LanguageServerErrorCode.NotFile,
'Invalid FileSegment or Path',
)
break
}
const file = await readFile(pathSegments(path))
if (!file) {
response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found')
break
}
const start = Number(segment.byteOffset())
const slice = file.slice(start, start + Number(segment.length()))
const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary')
const bytesOffset = EnsoDigest.createBytesVector(builder, digest)
const checksumOffset = EnsoDigest.createEnsoDigest(builder, bytesOffset)
response = {
type: OutboundPayload.CHECKSUM_BYTES_REPLY,
offset: ChecksumBytesReply.createChecksumBytesReply(builder, checksumOffset),
}
break
}
}
if (!response) return
const correlationUuid = binaryMessage.messageId()
if (!correlationUuid) return
const rootTable = OutboundMessage.createOutboundMessage(
builder,
createMessageId,
createCorrelationId(correlationUuid),
response.type,
response.offset,
)
send(builder.finish(rootTable).toArrayBuffer())
}
}