Files
mudita-center/libs/api-devices-testing/src/file-transfer.spec.ts
Daniel Karski 6450a175c8 Release v4.0.0 - to stage (#2803)
Co-authored-by: Michał Kurczewski <michalkurczewski94@gmail.com>
Co-authored-by: Michał Kurczewski <michal@kurczewski.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-30 15:49:30 +02:00

258 lines
7.5 KiB
TypeScript

/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import path from "path"
import { crc32 } from "node:zlib"
import { readFileSync } from "fs-extra"
import {
buildFileTransferDeleteRequest,
buildFileTransferPostRequest,
buildPreFileTransferPostRequest,
PreFileTransferPostResponseValidator,
} from "devices/api-device/models"
import { execPromise } from "app-utils/main"
import { delay } from "app-utils/common"
import { getService } from "./helpers/api-device-test-service"
const validTargetPath = "/storage/emulated/0/testfile"
const invalidTargetPath = "/storage/emulated/1/testfile"
const sourcePath = path.resolve(__dirname, "./test-files/sample.png")
describe("File transfer", () => {
afterEach(async () => {
await removeFile(validTargetPath)
})
it("should send file into device and return valid responses", async () => {
let transferId = -1
const file = readFileSync(sourcePath, {
encoding: "base64",
})
const crc = (crc32(file) >>> 0).toString(16).toLowerCase().padStart(8, "0")
const fileSize = file.length
let preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: validTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
while (preTransferResponse.status === 206) {
await delay(500)
preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: validTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
}
expect(preTransferResponse.status).toBe(200)
if (preTransferResponse.status === 200) {
const preTransferResponseData =
PreFileTransferPostResponseValidator.parse(preTransferResponse.body)
const chunkSize = preTransferResponseData.chunkSize
const fileChunksCount = Math.ceil(fileSize / chunkSize)
for (let chunkNumber = 0; chunkNumber < fileChunksCount; chunkNumber++) {
transferId = preTransferResponseData.transferId
const data = file.slice(
chunkNumber * chunkSize,
(chunkNumber + 1) * chunkSize
)
const fileTransferResponse = await getService().request(
buildFileTransferPostRequest({
transferId: transferId,
chunkNumber: chunkNumber + 1,
data,
})
)
if (chunkNumber === fileChunksCount - 1) {
expect(fileTransferResponse.status).toBe(200)
} else {
expect(fileTransferResponse.status).toBe(206)
}
}
const isFilePresent = await isFileExists(validTargetPath)
expect(isFilePresent).toBe(true)
}
}, 60_000)
it("should return error response for invalid targetPath", async () => {
let transferId = -1
const file = readFileSync(sourcePath, {
encoding: "base64",
})
const crc = (crc32(file) >>> 0).toString(16).toLowerCase().padStart(8, "0")
const fileSize = file.length
let preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: invalidTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
while (preTransferResponse.status === 206) {
await delay(500)
preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: invalidTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
}
expect(preTransferResponse.status).toBe(500)
if (preTransferResponse.status === 200) {
const preTransferResponseData =
PreFileTransferPostResponseValidator.parse(preTransferResponse.body)
const chunkSize = preTransferResponseData.chunkSize
const fileChunksCount = Math.ceil(fileSize / chunkSize)
for (let chunkNumber = 0; chunkNumber < fileChunksCount; chunkNumber++) {
transferId = preTransferResponseData.transferId
const data = file.slice(
chunkNumber * chunkSize,
(chunkNumber + 1) * chunkSize
)
const fileTransferResponse = await getService().request(
buildFileTransferPostRequest({
transferId: transferId,
chunkNumber: chunkNumber + 1,
data,
})
)
expect(fileTransferResponse.status).toBe(200)
}
const isFilePresent = await isFileExists(invalidTargetPath)
expect(isFilePresent).toBe(false)
}
}, 60_000)
it("file transfer delete should return success with 200 status for valid targetPath", async () => {
let transferId = -1
const file = readFileSync(sourcePath, {
encoding: "base64",
})
const crc = (crc32(file) >>> 0).toString(16).toLowerCase().padStart(8, "0")
const fileSize = file.length
let preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: validTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
while (preTransferResponse.status === 206) {
await delay(500)
preTransferResponse = await getService().request(
buildPreFileTransferPostRequest({
filePath: validTargetPath,
fileSize: fileSize,
crc32: crc,
})
)
}
expect(preTransferResponse.status).toBe(200)
if (preTransferResponse.status === 200) {
const preTransferResponseData =
PreFileTransferPostResponseValidator.parse(preTransferResponse.body)
const chunkSize = preTransferResponseData.chunkSize
const fileChunksCount = Math.ceil(fileSize / chunkSize)
for (let chunkNumber = 0; chunkNumber < fileChunksCount; chunkNumber++) {
transferId = preTransferResponseData.transferId
const data = file.slice(
chunkNumber * chunkSize,
(chunkNumber + 1) * chunkSize
)
const fileTransferResponse = await getService().request(
buildFileTransferPostRequest({
transferId: transferId,
chunkNumber: chunkNumber + 1,
data,
})
)
if (chunkNumber === fileChunksCount - 1) {
expect(fileTransferResponse.status).toBe(200)
} else {
expect(fileTransferResponse.status).toBe(206)
}
}
const isFilePresent = await isFileExists(validTargetPath)
expect(isFilePresent).toBe(true)
}
expect(transferId).toBeGreaterThan(-1)
const deleteResult = await getService().request(
buildFileTransferDeleteRequest({
fileTransferId: transferId,
})
)
expect(deleteResult.status).toBe(200)
}, 60_000)
it("file transfer delete should return success with 207 status for invalid targetPath", async () => {
const deleteResult = await getService().request(
buildFileTransferDeleteRequest({
fileTransferId: -10,
})
)
expect(deleteResult.status).toBe(207)
}, 60_000)
const removeFile = async (targetPath: string): Promise<void> => {
try {
const command = `adb shell rm ${targetPath}`
await execPromise(
command,
process.platform === "win32" ? "powershell.exe" : undefined
)
} catch {
return
}
}
const isFileExists = async (targetPath: string): Promise<boolean> => {
try {
const command = `adb shell "[ -f ${targetPath} ] && echo 1 || echo 0"`
const stdout =
(await execPromise(
command,
process.platform === "win32" ? "powershell.exe" : undefined
)) ?? ""
return stdout.trim() === "1"
} catch {
return false
}
}
})