mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
test: prove 2.8 to 2.9.0 upgrade is lossless and fix what it caught
This commit is contained in:
1 parent
2ee9b3592c
commit
f779d6a08e
37 files changed
+5184
-377
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
export function isInside(file: string, dir: string): boolean {
|
||||
return path.resolve(file).startsWith(`${path.resolve(dir)}${path.sep}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a finished recording someone else wrote (guacd's, in remote
|
||||
* desktop's recordings folder) under this plugin's data folder, which is the
|
||||
* only place playback and retention read from. Returns where it ended up;
|
||||
* a file that is not there is left alone.
|
||||
*/
|
||||
export async function adoptRecordingFile(
|
||||
source: string,
|
||||
dataDir: string,
|
||||
): Promise<string> {
|
||||
if (isInside(source, dataDir)) return source;
|
||||
const target = path.join(
|
||||
dataDir,
|
||||
"session_recordings",
|
||||
path.basename(path.dirname(source)),
|
||||
path.basename(source),
|
||||
);
|
||||
try {
|
||||
await fs.access(source);
|
||||
} catch {
|
||||
return source;
|
||||
}
|
||||
await fs.mkdir(path.dirname(target), { recursive: true });
|
||||
try {
|
||||
await fs.rename(source, target);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
||||
await fs.copyFile(source, target);
|
||||
await fs.unlink(source);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import type { SessionRecordingRepository } from "./repository.js";
|
||||
import { adoptRecordingFile } from "./files.js";
|
||||
|
||||
/**
|
||||
* recordings.writer v1, the service ssh-terminal calls to record sessions.
|
||||
@@ -40,8 +41,8 @@ export interface RecordingsWriterV1 {
|
||||
/**
|
||||
* Inserts a row for a recording a caller already wrote to disk itself
|
||||
* (remote desktop's guacd recordings), rather than streaming through
|
||||
* open()/append(). The caller already decided to record; this only writes
|
||||
* the row.
|
||||
* open()/append(). The caller already decided to record. The file is
|
||||
* moved under this plugin's data folder, where playback reads it.
|
||||
*/
|
||||
createFinished: (input: {
|
||||
hostId: number;
|
||||
@@ -117,7 +118,15 @@ export function createRecordingsWriter(
|
||||
|
||||
async createFinished(input) {
|
||||
const username = await repository.usernameFor(input.userId);
|
||||
const row = await repository.create({ ...input, username });
|
||||
const recordingPath = await adoptRecordingFile(
|
||||
input.recordingPath,
|
||||
await ctx.files.dataDir(),
|
||||
).catch(() => input.recordingPath);
|
||||
const row = await repository.create({
|
||||
...input,
|
||||
recordingPath,
|
||||
username,
|
||||
});
|
||||
return { id: row.id };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -172,6 +172,41 @@ describe("recordings.writer", () => {
|
||||
expect(row.id).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("createFinished moves guacd's file under the data folder, where playback reads it", async () => {
|
||||
const { mock, writer } = await setup();
|
||||
const guacdDir = await fs.mkdtemp(path.join(os.tmpdir(), "guacd-"));
|
||||
const source = path.join(guacdDir, "guacamole", "rdp-1.guac");
|
||||
await fs.mkdir(path.dirname(source), { recursive: true });
|
||||
await fs.writeFile(source, "4.size,1.0;");
|
||||
|
||||
const row = await writer.createFinished({
|
||||
hostId: 7,
|
||||
userId: "user-1",
|
||||
startedAt: new Date().toISOString(),
|
||||
endedAt: new Date().toISOString(),
|
||||
duration: 10,
|
||||
recordingPath: source,
|
||||
protocol: "rdp",
|
||||
format: "guacamole",
|
||||
});
|
||||
|
||||
const target = path.join(
|
||||
await mock.ctx.files.dataDir(),
|
||||
"session_recordings",
|
||||
"guacamole",
|
||||
"rdp-1.guac",
|
||||
);
|
||||
await expect(fs.readFile(target, "utf8")).resolves.toBe("4.size,1.0;");
|
||||
await expect(fs.access(source)).rejects.toThrow();
|
||||
const stored = db!.sqlite
|
||||
.prepare(
|
||||
"SELECT recording_path FROM p_session_recording_session_recordings WHERE id = ?",
|
||||
)
|
||||
.get(row.id) as { recording_path: string };
|
||||
expect(stored.recording_path).toBe(target);
|
||||
await fs.rm(guacdDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("enabledFor follows the host's recording switch", async () => {
|
||||
const { mock, writer } = await setup();
|
||||
await mock.ctx.settings.setHost(7, "enableSessionRecording", true);
|
||||
|
||||
Reference in new issue
Block a user