76 lines
2.7 KiB
JavaScript
76 lines
2.7 KiB
JavaScript
|
|
import assert from "node:assert/strict";
|
||
|
|
import { mkdtemp, readFile } from "node:fs/promises";
|
||
|
|
import os from "node:os";
|
||
|
|
import path from "node:path";
|
||
|
|
import test from "node:test";
|
||
|
|
import { DatabaseClient, normalizeSqlForMysql } from "../lib/database.ts";
|
||
|
|
import { LocalObjectStore, normalizeObjectKey } from "../lib/object-store.ts";
|
||
|
|
import { requestUsesHttps } from "../lib/user-auth.ts";
|
||
|
|
|
||
|
|
test("normalizes the limited SQLite syntax still used by business queries", () => {
|
||
|
|
assert.match(
|
||
|
|
normalizeSqlForMysql("INSERT OR IGNORE INTO collection_runs (id) VALUES (?)"),
|
||
|
|
/^INSERT IGNORE INTO/,
|
||
|
|
);
|
||
|
|
const upsert = normalizeSqlForMysql(
|
||
|
|
"INSERT INTO accounts (id, nickname) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET nickname = excluded.nickname",
|
||
|
|
);
|
||
|
|
assert.match(upsert, /ON DUPLICATE KEY UPDATE/);
|
||
|
|
assert.match(upsert, /nickname = VALUES\(nickname\)/);
|
||
|
|
assert.equal(
|
||
|
|
normalizeSqlForMysql("SELECT datetime(publish_time, '+7 days')"),
|
||
|
|
"SELECT DATE_ADD(publish_time, INTERVAL 7 DAY)",
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("normalizes ISO timestamps before binding to MySQL DATETIME", async () => {
|
||
|
|
let receivedParams;
|
||
|
|
const database = new DatabaseClient({
|
||
|
|
async execute(_sql, params) {
|
||
|
|
receivedParams = params;
|
||
|
|
return [{ affectedRows: 1, insertId: 0 }];
|
||
|
|
},
|
||
|
|
});
|
||
|
|
await database
|
||
|
|
.prepare("INSERT INTO auth_sessions (expires_at) VALUES (?)")
|
||
|
|
.bind("2026-08-11T02:03:04.567Z")
|
||
|
|
.run();
|
||
|
|
assert.deepEqual(receivedParams, ["2026-08-11 02:03:04.567"]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("marks login cookies secure behind the Nginx HTTPS proxy", () => {
|
||
|
|
assert.equal(
|
||
|
|
requestUsesHttps(
|
||
|
|
new Request("http://app:3000/api/auth/login", {
|
||
|
|
headers: { "x-forwarded-proto": "https" },
|
||
|
|
}),
|
||
|
|
),
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
assert.equal(requestUsesHttps(new Request("http://localhost/login")), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("stores uploaded objects under a safe persistent directory", async () => {
|
||
|
|
const directory = await mkdtemp(path.join(os.tmpdir(), "koc-object-store-"));
|
||
|
|
const store = new LocalObjectStore(directory);
|
||
|
|
await store.put("creator-center/dist-1/shot.png", Uint8Array.from([1, 2, 3]), {
|
||
|
|
httpMetadata: { contentType: "image/png" },
|
||
|
|
});
|
||
|
|
const object = await store.get("creator-center/dist-1/shot.png");
|
||
|
|
assert.ok(object);
|
||
|
|
assert.deepEqual([...new Uint8Array(await object.arrayBuffer())], [1, 2, 3]);
|
||
|
|
const headers = new Headers();
|
||
|
|
object.writeHttpMetadata(headers);
|
||
|
|
assert.equal(headers.get("Content-Type"), "image/png");
|
||
|
|
assert.equal(
|
||
|
|
JSON.parse(
|
||
|
|
await readFile(
|
||
|
|
path.join(directory, "creator-center/dist-1/shot.png.metadata.json"),
|
||
|
|
"utf8",
|
||
|
|
),
|
||
|
|
).contentType,
|
||
|
|
"image/png",
|
||
|
|
);
|
||
|
|
assert.throws(() => normalizeObjectKey("../secret"), /不安全/);
|
||
|
|
});
|