Files
Termix/plugins/docker/tests/backend/fake-ssh.ts
T

85 lines
2.2 KiB
TypeScript

import { EventEmitter } from "node:events";
/** An ssh2 exec channel: stdout on "data", stderr on `stderr`, then "close". */
export class FakeStream extends EventEmitter {
stderr = new EventEmitter();
written: string[] = [];
ended = false;
window: [number, number] | null = null;
write(data: string) {
this.written.push(data);
return true;
}
end() {
this.ended = true;
}
setWindow(rows: number, cols: number) {
this.window = [rows, cols];
}
/** Emits output and exits, on the next tick like a real channel. */
finish(stdout: string, code = 0, stderr = "") {
setImmediate(() => {
if (stdout) this.emit("data", Buffer.from(stdout));
if (stderr) this.stderr.emit("data", Buffer.from(stderr));
this.emit("close", code);
});
}
}
export type ExecReply =
| { stdout?: string; stderr?: string; code?: number }
| ((stream: FakeStream) => void);
/**
* An ssh2 Client that answers exec by the first matching pattern. A reply
* may be a function for a long-lived channel such as a console.
*/
export class FakeClient extends EventEmitter {
commands: string[] = [];
streams: FakeStream[] = [];
ended = false;
constructor(private replies: Array<[RegExp, ExecReply]> = []) {
super();
}
reply(pattern: RegExp, reply: ExecReply) {
this.replies.unshift([pattern, reply]);
}
exec(
command: string,
optionsOrCallback: unknown,
maybeCallback?: (err: Error | undefined, stream: FakeStream) => void,
) {
const callback = (
typeof optionsOrCallback === "function"
? optionsOrCallback
: maybeCallback
) as (err: Error | undefined, stream: FakeStream) => void;
this.commands.push(command);
const stream = new FakeStream();
this.streams.push(stream);
const match = this.replies.find(([pattern]) => pattern.test(command));
callback(undefined, stream);
if (!match) {
stream.finish("");
return this;
}
const reply = match[1];
if (typeof reply === "function") reply(stream);
else stream.finish(reply.stdout ?? "", reply.code ?? 0, reply.stderr ?? "");
return this;
}
end() {
if (this.ended) return;
this.ended = true;
this.emit("close");
}
}