mirror-cockpit-project-cockpit/pkg/base1/test-file.ts
Viktor Szépe 5ff1ec08a0
Fix typos throughout codebase (#23813)
Several typos and common misspellings of words exists throughout Cockpit. This fixes the spellings.
2026-10-14 14:02:30 +02:00

535 lines
18 KiB
TypeScript

// SPDX-License-Identifier: LGPL-2.1-or-later
import cockpit from "cockpit";
import QUnit from "qunit-tests";
let dir = "/uninitialized";
QUnit.test("simple read", async assert => {
const file = cockpit.file(dir + "/foo");
assert.equal(file.path, dir + "/foo", "file has path");
assert.equal(await file.read(), "1234\n", "correct result");
});
QUnit.test("read non-existent", async assert => {
assert.equal(await cockpit.file(dir + "/blah").read(), null, "correct result");
});
QUnit.test("parse read", async assert => {
const resp = await cockpit.file(dir + "/foo.json", { syntax: JSON }).read();
assert.deepEqual(resp, { foo: 12 }, "correct result");
});
QUnit.test("parse read error", async assert => {
try {
await cockpit.file(dir + "/foo.bin", { syntax: JSON }).read();
assert.ok(false, "should have failed");
} catch (error) {
assert.ok(error instanceof SyntaxError, "got SyntaxError error");
}
});
QUnit.test("binary read", async assert => {
const resp = await cockpit.file(dir + "/foo.bin", { binary: true }).read();
assert.deepEqual(resp, new Uint8Array([0, 1, 2, 3]), "correct result");
});
QUnit.test("read non-regular", async assert => {
try {
await cockpit.file(dir, { binary: true }).read();
assert.ok(false, "should have failed");
} catch (error) {
assert.equal((error as cockpit.BasicError).problem, "internal-error", "got error");
}
});
QUnit.test("read large", async assert => {
const resp = await cockpit.file(dir + "/large.bin", { binary: true }).read();
assert.equal(resp.length, 512 * 1024, "correct result");
});
QUnit.test("read too large", async assert => {
try {
await cockpit.file(dir + "/large.bin", { binary: true, max_read_size: 8 * 1024 }).read();
assert.ok(false, "should have failed");
} catch (error) {
assert.equal((error as cockpit.BasicError).problem, "too-large", "got error");
}
});
/* regression: passing 'binary: false' made cockpit-ws close the whole connection */
QUnit.test("binary false", async assert => {
await cockpit.file(dir + "/foo.bin", { binary: false }).read();
assert.ok(true, "did not crash");
});
QUnit.test("simple replace", async assert => {
await cockpit.file(dir + "/bar").replace("4321\n");
const res = await cockpit.spawn(["cat", dir + "/bar"]);
assert.equal(res, "4321\n", "correct content");
});
QUnit.test("empty replace", async assert => {
await cockpit.file(`${dir}/bar`).replace("");
const res = await cockpit.spawn(["cat", `${dir}/bar`]);
assert.equal(res, "", "correct content");
});
QUnit.test("stringify replace", async assert => {
await cockpit.file(dir + "/bar", { syntax: JSON }).replace({ foo: 4321 });
const res = await cockpit.spawn(["cat", dir + "/bar"]);
assert.deepEqual(JSON.parse(res), { foo: 4321 }, "correct content");
});
QUnit.test("stringify replace error", async assert => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cycle: any = { };
cycle.me = cycle;
try {
await cockpit.file(dir + "/bar", { syntax: JSON }).replace(cycle);
assert.ok(false, "should have failed");
} catch (error) {
assert.ok(error instanceof TypeError, "got stringify error");
}
});
QUnit.test("binary replace", async assert => {
await cockpit.file(dir + "/bar", { binary: true }).replace(new Uint8Array([3, 2, 1, 0]));
const res = await cockpit.spawn(["cat", dir + "/bar"], { binary: true });
assert.deepEqual(res, new Uint8Array([3, 2, 1, 0]), "correct content");
});
QUnit.test("replace large", async assert => {
const str = new Array(23 * 1023).join('abcdef12345');
await cockpit.file(dir + "/large").replace(str);
const res = await cockpit.spawn(["cat", dir + "/large"]);
assert.equal(res.length, str.length, "correct large length");
assert.ok(res == str, "correct large data");
});
QUnit.test("binary replace large", async assert => {
const data = new Uint8Array(249 * 1023);
const len = data.byteLength;
for (let i = 0; i < len; i++)
data[i] = i % 233;
await cockpit.file(dir + "/large-binary", { binary: true }).replace(data);
const res = await cockpit.spawn(["cat", dir + "/large-binary"], { binary: true });
let eq = true;
assert.equal(res.byteLength, 249 * 1023, "check length");
assert.equal(res.byteLength, data.byteLength, "correct large length");
for (let i = 0; i < res.byteLength; i++) {
if (res[i] !== data[i] || res[i] === undefined) {
eq = false;
break;
}
}
assert.ok(eq, "got back same data");
});
QUnit.test("fsreplace1 no path", async assert => {
const invalid = cockpit.channel({ payload: "fsreplace1", attrs: {} });
await assert.rejects(invalid.wait(), /attribute 'path' required/, "rejects no path");
});
QUnit.test("fsreplace1 attrs", async assert => {
await cockpit.init();
assert.propContains(cockpit.info.channels, { fsreplace1: ['attrs'] });
const valid = cockpit.channel({ payload: "fsreplace1", path: `${dir}/tmp`, attrs: {} });
await valid.wait();
valid.close(); // no-op
const invalid = cockpit.channel({ payload: "fsreplace1", path: `${dir}/tmp`, attrs: { foo: 1 } });
await assert.rejects(invalid.wait(), /"attrs" contains unsupported key\(s\): foo/, "rejects non-empty attrs");
});
QUnit.test("remove", async assert => {
const exists = await cockpit.spawn(["bash", "-c", "test -f " + dir + "/bar && echo exists"]);
assert.equal(exists, "exists\n", "exists");
await cockpit.file(dir + "/bar").replace(null);
const res = await cockpit.spawn(["bash", "-c", "test -f " + dir + "/bar || echo gone"]);
assert.equal(res, "gone\n", "gone");
});
QUnit.test("abort replace", async assert => {
const done = assert.async();
assert.expect(0);
const file = cockpit.file(dir + "/bar");
let n = 0;
function start_after_two() {
n += 1;
if (n == 2)
done();
}
const first = file.replace("1234\n");
const second = file.replace("abcd\n");
try {
await first;
throw new Error("unexpectedly succeeded first promise");
} catch (_error) {
start_after_two();
}
await second;
start_after_two();
});
QUnit.test("replace with tag", async assert => {
const done = assert.async();
const file = cockpit.file(dir + "/barfoo");
file.read()
// @ts-expect-error cannot represent read promise signature with tag
.then(async (content, tag_1) => {
assert.equal(content, null, "file does not exist");
assert.equal(tag_1, "-", "first tag is -");
const tag_2 = await file.replace("klmn\n", tag_1);
assert.notEqual(tag_2, "-", "second tag is not -");
const tag_3 = await file.replace("KLMN\n", tag_2);
assert.notEqual(tag_3, tag_2, "third tag is different");
try {
await file.replace("opqr\n", tag_2);
assert.ok(false, "should have failed");
} catch (error) {
assert.equal((error as cockpit.BasicError).problem, "change-conflict", "wrong tag is rejected");
done();
}
});
});
QUnit.test("modify", async assert => {
const file = cockpit.file(dir + "/quux");
let n = 0;
await file.modify(old => {
n += 1;
assert.equal(old, null, "no old content");
return "ABCD\n";
});
assert.equal(n, 1, "callback called once");
n = 0;
await file.modify(old => {
n += 1;
assert.equal(old, "ABCD\n", "correct old content");
return "dcba\n";
});
assert.equal(n, 1, "callback called once");
assert.equal(await cockpit.spawn(["cat", dir + "/quux"]), "dcba\n", "correct content");
// make sure that writing "" results in an empty file, not a deleted one
n = 0;
await file.modify(old => {
n += 1;
assert.equal(old, "dcba\n", "correct old content");
return "";
});
assert.equal(n, 1, "callback called once");
assert.equal(await cockpit.spawn(["cat", dir + "/quux"]), "", "correct content");
// make sure that writing null deletes the file
n = 0;
await file.modify(old => {
n += 1;
assert.equal(old, "", "correct old content");
return null;
});
assert.equal(n, 1, "callback called once");
assert.rejects(cockpit.spawn(["cat", dir + "/quux"]), /No such file or directory/, "file deleted");
});
QUnit.test("modify with conflict", async assert => {
const done = assert.async();
assert.expect(6);
const file = cockpit.file(dir + "/baz");
let n = 0;
file
.modify(old => {
n += 1;
assert.equal(old, null, "no old content");
return "ABCD\n";
})
.finally(() => assert.equal(n, 1, "callback called once"))
// @ts-expect-error cannot represent modify promise signature with tag
.then(async (content, tag) => {
let n = 0;
await cockpit.spawn(["bash", "-c", "sleep 1; echo XYZ > " + dir + "/baz"]);
await file.modify(old => {
cockpit.assert(old !== null, "old is null");
n += 1;
if (n == 1)
assert.equal(old, "ABCD\n", "correct old (out-of-date) content");
else
assert.equal(old, "XYZ\n", "correct old (current) content");
return old.toLowerCase();
}, content, tag);
assert.equal(n, 2, "callback called twice");
const res = await cockpit.spawn(["cat", dir + "/baz"]);
assert.equal(res, "xyz\n", "correct content");
done();
});
});
QUnit.test("watching", assert => {
const done = assert.async();
assert.expect(7);
const file = cockpit.file(dir + "/foobar");
let n = 0;
const watch = file.watch((content, tag) => {
n += 1;
if (n == 1) {
assert.equal(content, null, "initially non-existent");
assert.equal(tag, "-", "empty tag");
cockpit.spawn(["bash", "-c", "cd " + dir + " && echo 1234 > foobar.tmp && mv foobar.tmp foobar"]);
} else if (n == 2) {
assert.equal(content, "1234\n", "correct new content");
assert.notEqual(tag, "-");
cockpit.assert(tag !== null, "tag is null");
assert.ok(tag.length > 5, "tag has a reasonable size");
cockpit.spawn(["bash", "-c", "rm " + dir + "/foobar"]);
} else if (n == 3) {
assert.equal(content, null, "finally non-existent");
assert.equal(tag, "-", "empty tag");
watch.remove();
done();
}
});
});
QUnit.test("binary watching", assert => {
const done = assert.async();
assert.expect(3);
const file = cockpit.file(dir + "/foobar", { binary: true });
let n = 0;
const watch = file.watch(content => {
n += 1;
if (n == 1) {
assert.equal(content, null, "initially non-existent");
cockpit.spawn(["bash", "-c", "cd " + dir + " && echo '//8BAg==' | base64 -d > foobar.tmp && mv foobar.tmp foobar"]);
} else if (n == 2) {
assert.deepEqual(content, new Uint8Array([255, 255, 1, 2]), "correct new content");
cockpit.spawn(["bash", "-c", "rm " + dir + "/foobar"]);
} else if (n == 3) {
assert.equal(content, null, "finally non-existent");
watch.remove();
done();
}
});
});
QUnit.test("syntax watching", assert => {
const done = assert.async();
assert.expect(3);
const file = cockpit.file(dir + "/foobar.json", { syntax: JSON });
let n = 0;
const watch = file.watch((content, _tag, err) => {
n += 1;
if (n == 1) {
assert.equal(content, null, "initially non-existent");
cockpit.spawn(["bash", "-c", "cd " + dir + " && echo '[ 1, 2, 3, 4 ]' > foobar.json.tmp && mv foobar.json.tmp foobar.json"]);
} else if (n == 2) {
assert.deepEqual(content, [1, 2, 3, 4], "correct new content");
cockpit.spawn(["bash", "-c", "echo 'hi-there-this-is-not-json' > " + dir + "/foobar.json"]);
} else if (n == 3) {
assert.ok(err instanceof SyntaxError, "got SyntaxError error");
watch.remove();
done();
} else
assert.ok(false, "not reached");
});
});
QUnit.test("watching without reading", assert => {
const done = assert.async();
assert.expect(7);
const file = cockpit.file(dir + "/foobar");
let n = 0;
const watch = file.watch((content, tag) => {
n += 1;
if (n == 1) {
assert.equal(content, null, "initially non-existent");
assert.equal(tag, "-", "empty tag");
cockpit.spawn(["bash", "-c", "cd " + dir + " && echo 1234 > foobar.tmp && mv foobar.tmp foobar"]);
} else if (n == 2) {
assert.equal(content, null, "no content as reading is disabled");
assert.notEqual(tag, "-");
cockpit.assert(tag !== null, "tag is null");
assert.ok(tag.length > 5, "tag has a reasonable size");
cockpit.spawn(["bash", "-c", "rm " + dir + "/foobar"]);
} else if (n == 3) {
assert.equal(content, null, "finally non-existent");
assert.equal(tag, "-", "empty tag");
watch.remove();
done();
}
}, { read: false });
});
QUnit.test("watching without reading pre-created", async assert => {
const done = assert.async();
assert.expect(3);
// Pre-create fsinfo test file
const file = cockpit.file(dir + "/fsinfo");
await file.replace("1234");
const watch = file.watch((content, tag) => {
assert.equal(content, null, "non-existent because read is false");
assert.notEqual(tag, null, "non empty tag");
cockpit.assert(tag !== null, "tag is null");
assert.equal(tag.startsWith("1:"), true, "tag always starts with 1:");
watch.remove();
done();
}, { read: false });
});
QUnit.test("watching directory", assert => {
const done = assert.async();
assert.expect(20);
let n = 0;
const watch = cockpit.channel({ payload: "fswatch1", path: dir });
watch.addEventListener("message", (_event, payload) => {
const msg = JSON.parse(payload);
n += 1;
if (n == 1) {
assert.equal(msg.event, "created", "world.txt created");
assert.equal(msg.path, dir + "/world.txt");
assert.equal(msg.type, "file");
assert.notEqual(msg.tag, "-");
} else if (n == 2) {
assert.equal(msg.event, "changed", "world.txt changed");
assert.equal(msg.path, dir + "/world.txt");
assert.notEqual(msg.tag, "-");
} else if (n == 3) {
assert.equal(msg.event, "done-hint", "world.txt done-hint");
assert.equal(msg.path, dir + "/world.txt");
assert.notEqual(msg.tag, "-");
cockpit.spawn(["chmod", "001", `${dir}/world.txt`]);
} else if (n == 4) {
assert.equal(msg.event, "attribute-changed", "world.txt attribute-changed");
assert.equal(msg.path, dir + "/world.txt");
assert.notEqual(msg.tag, "-");
cockpit.spawn(["rm", `${dir}/world.txt`]);
} else if (n == 5) {
assert.equal(msg.event, "deleted", "world.txt deleted");
assert.equal(msg.path, dir + "/world.txt");
assert.equal(msg.tag, "-");
cockpit.spawn(["mkdir", `${dir}/somedir`]);
} else if (n == 6) {
assert.equal(msg.event, "created", "somedir created");
assert.equal(msg.path, dir + "/somedir");
assert.equal(msg.type, "directory");
assert.notEqual(msg.tag, "-");
watch.close();
done();
}
});
// trigger the first event
cockpit.spawn(["sh", "-c", `echo hello > ${dir}/world.txt`]);
});
QUnit.test("watching error", async assert => {
const dir = await cockpit.spawn([
'sh', '-c',
`
cd "$(mktemp -d)"
echo -n "$(pwd)"
mkdir dir
echo dir file > dir/dir-file.txt
echo do not read this > dir-file.xtx
chmod 0 dir
`
]);
const file = cockpit.file(`${dir}/dir/file`);
try {
await new Promise<void>(resolve => {
file.watch((content, tag, error) => {
assert.equal(content, null);
assert.equal(tag, null);
cockpit.assert(error !== null, "error is null");
assert.equal(error.problem, 'access-denied');
resolve();
});
});
} finally {
file.close();
await cockpit.spawn(["chmod", "-R", "u+rwX", dir]);
await cockpit.spawn(["rm", "-rf", dir]);
}
});
QUnit.test("closing", assert => {
const done = assert.async();
assert.expect(2);
const file = cockpit.file(dir + "/foobarbaz");
const watch = file.watch(changed);
let n = 0;
function start_after_two() {
n += 1;
if (n == 2) {
watch.remove();
done();
}
}
file.read()
.then(() => {
assert.ok(false, "read didn't complete");
})
.catch(error => {
assert.equal(error.problem, "cancelled", "read got cancelled");
start_after_two();
});
function changed(_content: string | null, _tag: string | null, err: cockpit.BasicError | null) {
if (err) {
assert.equal(err.problem, "cancelled", "watch got cancelled");
start_after_two();
} else {
assert.ok(false, "not reached");
}
}
file.close();
});
QUnit.test("channel options", async assert => {
const data = await cockpit.file(dir + "/foo", { binary: true }).read();
assert.ok(data instanceof Uint8Array, "options applied, got binary data");
});
QUnit.test("remove testdir", async assert => {
await cockpit.spawn(["rm", "-rf", dir]);
assert.ok(true, "did not crash");
});
(async () => {
const resp = await cockpit.spawn(["bash", "-c", "d=$(mktemp -d); echo '1234' >$d/foo; echo '{ \"foo\": 12 }' >$d/foo.json; echo -en '\\x00\\x01\\x02\\x03' >$d/foo.bin; dd if=/dev/zero of=$d/large.bin bs=1k count=512; echo $d"]);
dir = resp.replace(/\n$/, "");
QUnit.start();
})();