mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
254 lines
8.9 KiB
Python
254 lines
8.9 KiB
Python
"""Behavior and wiring tests for large-paste attachment conversion."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_ROOT = Path(__file__).resolve().parent.parent
|
|
_HELPER = _ROOT / "turnstone/shared_static/composer_paste_text.js"
|
|
_COMPOSER = _ROOT / "turnstone/shared_static/composer.js"
|
|
_ATTACHMENTS = _ROOT / "turnstone/shared_static/composer_attachments.js"
|
|
_INTERACTIVE = _ROOT / "turnstone/shared_static/interactive.js"
|
|
_UI_APP = _ROOT / "turnstone/ui/static/app.js"
|
|
_CONSOLE_APP = _ROOT / "turnstone/console/static/app.js"
|
|
_COORDINATOR = _ROOT / "turnstone/console/static/coordinator/coordinator.js"
|
|
|
|
|
|
def test_paste_text_helper_behavior(tmp_path: Path) -> None:
|
|
"""Execute the real ESM and pin its fixed threshold, byte cap, and dedup."""
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node binary not available on PATH")
|
|
|
|
module = tmp_path / "composer_paste_text.mjs"
|
|
module.write_text(_HELPER.read_text(encoding="utf-8"), encoding="utf-8")
|
|
script = tmp_path / "paste_harness.mjs"
|
|
module_url = json.dumps(module.as_uri())
|
|
script.write_text(
|
|
f"const moduleUrl = {module_url};\n"
|
|
+ r"""
|
|
import { Blob, File } from "node:buffer";
|
|
globalThis.Blob = Blob;
|
|
globalThis.File = File;
|
|
globalThis.window = globalThis;
|
|
|
|
function check(condition, message) {
|
|
if (!condition) throw new Error(message);
|
|
}
|
|
|
|
const paste = await import(moduleUrl);
|
|
check(paste.PASTE_ATTACHMENT_CHARS === 2000, "fixed threshold drifted");
|
|
check(
|
|
paste.pasteTextToFile("x".repeat(1999)) === null,
|
|
"below-threshold text converted",
|
|
);
|
|
const exactText = "x".repeat(2000);
|
|
const exact = paste.pasteTextToFile(exactText);
|
|
check(exact === null, "exact-threshold text converted");
|
|
const convertedText = "x".repeat(2001);
|
|
const converted = paste.pasteTextToFile(convertedText);
|
|
check(converted instanceof File, "above-threshold text did not convert");
|
|
check(converted.name === "pasted-text.txt", "filename drifted");
|
|
check(converted.type === "text/plain", "MIME drifted");
|
|
check(converted.size === 2001, "byte size drifted");
|
|
check(
|
|
(await converted.text()) === convertedText,
|
|
"file content did not round-trip",
|
|
);
|
|
check(
|
|
paste.pasteTextToFile("") === null,
|
|
"empty text converted",
|
|
);
|
|
check(
|
|
paste.pasteTextToFile("😀".repeat(2000)) === null,
|
|
"UTF-16 code units were counted as characters",
|
|
);
|
|
check(
|
|
paste.pasteTextToFile("😀".repeat(2001)) instanceof File,
|
|
"Unicode code points above the threshold did not convert",
|
|
);
|
|
const cjkAtCap = "界".repeat(Math.floor(paste.TEXT_ATTACHMENT_MAX_BYTES / 3));
|
|
check(
|
|
paste.pasteTextToFile(cjkAtCap) instanceof File,
|
|
"text within the byte ceiling did not convert",
|
|
);
|
|
check(
|
|
paste.pasteTextToFile(cjkAtCap + "界") === null,
|
|
"multibyte text escaped the byte ceiling",
|
|
);
|
|
|
|
const sameText = "same".repeat(501);
|
|
const sameA = paste.pasteTextToFile(sameText);
|
|
const sameB = paste.pasteTextToFile(sameText);
|
|
const different = paste.pasteTextToFile("size".repeat(501));
|
|
check(
|
|
paste.isDuplicatePastedTextFile(sameB, [sameA]),
|
|
"identical synthesized pastes did not deduplicate",
|
|
);
|
|
check(
|
|
!paste.isDuplicatePastedTextFile(different, [sameA]),
|
|
"different same-size pastes were deduplicated",
|
|
);
|
|
check(
|
|
!paste.isDuplicatePastedTextFile(
|
|
new File([sameText], "ordinary.txt", { type: "text/plain" }),
|
|
[sameA],
|
|
),
|
|
"ordinary user files entered synthesized-paste dedup",
|
|
);
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
proc = subprocess.run(
|
|
["node", str(script)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=15,
|
|
)
|
|
assert proc.returncode == 0, f"paste harness failed:\n{proc.stderr}\n{proc.stdout}"
|
|
|
|
|
|
def test_attachment_snapshot_reports_in_flight_upload(tmp_path: Path) -> None:
|
|
"""A synthesized paste cannot disappear from a send-time snapshot while
|
|
its immediate upload is still resolving."""
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node binary not available on PATH")
|
|
|
|
script = tmp_path / "attachment_snapshot_harness.mjs"
|
|
module_url = json.dumps(_ATTACHMENTS.as_uri())
|
|
script.write_text(
|
|
f"const moduleUrl = {module_url};\n"
|
|
+ r"""
|
|
import { File } from "node:buffer";
|
|
globalThis.File = File;
|
|
globalThis.window = globalThis;
|
|
|
|
function fakeElement() {
|
|
return {
|
|
children: [],
|
|
dataset: {},
|
|
appendChild(child) {
|
|
this.children.push(child);
|
|
return child;
|
|
},
|
|
addEventListener() {},
|
|
querySelector() { return null; },
|
|
setAttribute() {},
|
|
remove() {},
|
|
};
|
|
}
|
|
globalThis.document = { createElement: () => fakeElement() };
|
|
|
|
let resolveUpload;
|
|
const uploadResponse = new Promise((resolve) => { resolveUpload = resolve; });
|
|
const attachmentsModule = await import(moduleUrl);
|
|
const controller = attachmentsModule.createAttachmentController({
|
|
chipsEl: fakeElement(),
|
|
getWsId: () => "ws-1",
|
|
authFetch: () => uploadResponse,
|
|
});
|
|
|
|
let snap = controller.snapshot();
|
|
if (snap.uploading || snap.attachment_ids.length)
|
|
throw new Error("empty controller reported an upload");
|
|
|
|
controller.upload(new File(["large paste"], "pasted-text.txt", {
|
|
type: "text/plain",
|
|
}));
|
|
snap = controller.snapshot();
|
|
if (!snap.uploading)
|
|
throw new Error("in-flight placeholder was omitted from snapshot state");
|
|
if (snap.attachments.length || snap.attachment_ids.length)
|
|
throw new Error("placeholder escaped into stable attachment arrays");
|
|
|
|
resolveUpload({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({
|
|
attachment_id: "attachment-1",
|
|
filename: "pasted-text.txt",
|
|
size_bytes: 11,
|
|
mime_type: "text/plain",
|
|
kind: "text",
|
|
}),
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
snap = controller.snapshot();
|
|
if (snap.uploading)
|
|
throw new Error("settled upload remained marked in flight");
|
|
if (snap.attachment_ids.join(",") !== "attachment-1")
|
|
throw new Error("settled upload was not sendable: " + snap.attachment_ids);
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
proc = subprocess.run(
|
|
["node", str(script)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=15,
|
|
)
|
|
assert proc.returncode == 0, (
|
|
f"attachment snapshot harness failed:\n{proc.stderr}\n{proc.stdout}"
|
|
)
|
|
|
|
|
|
def test_paste_text_wiring_guard_rails() -> None:
|
|
"""Guard every surface around the behavior-tested shared helper."""
|
|
helper = _HELPER.read_text(encoding="utf-8")
|
|
composer = _COMPOSER.read_text(encoding="utf-8")
|
|
attachments = _ATTACHMENTS.read_text(encoding="utf-8")
|
|
interactive = _INTERACTIVE.read_text(encoding="utf-8")
|
|
ui_app = _UI_APP.read_text(encoding="utf-8")
|
|
console_app = _CONSOLE_APP.read_text(encoding="utf-8")
|
|
coordinator = _COORDINATOR.read_text(encoding="utf-8")
|
|
|
|
assert "window.TurnstonePasteText" in helper
|
|
assert "PASTE_ATTACHMENT_CHARS = 2000" in helper
|
|
assert "paste_attachment_chars" not in helper
|
|
assert "loadPasteThresholdChars" not in helper
|
|
assert 'from "./composer_paste_text.js"' in composer
|
|
assert "pasteTextToFile(text" in composer
|
|
assert "2000" not in composer, "the threshold belongs in the shared helper"
|
|
assert composer.index("if (uploaded > 0)") < composer.index("pasteTextToFile(text")
|
|
assert "if (accepted !== false) e.preventDefault();" in composer
|
|
assert "if (pending.has(info.attachment_id))" in attachments
|
|
assert "pending.delete(placeholderId);" in attachments
|
|
assert "uploading: uploading" in attachments
|
|
|
|
assert "function _handleComposerPaste(" in ui_app
|
|
assert "if (files.length > 0)" in ui_app
|
|
assert "if (!textFile || addFiles([textFile]) === false) return false;" in ui_app
|
|
assert "_handleComposerPaste(event, _newWsAddFiles)" in ui_app
|
|
assert "_handleComposerPaste(e, _addDashboardFiles)" in ui_app
|
|
assert "initEl.onpaste" in ui_app
|
|
assert ui_app.count("Add a message to send with this attachment.") == 2
|
|
assert "_loadPasteAttachmentSetting" not in ui_app
|
|
|
|
assert "return _homeStageFile(file);" in console_app
|
|
assert "isDuplicatePastedTextFile(file, _homeStagedFiles)" in console_app
|
|
assert "Add a message to send with this attachment." in console_app
|
|
assert "_loadPasteAttachmentSetting" not in console_app
|
|
|
|
for pane in (interactive, coordinator):
|
|
assert "Add a message to send with this attachment." in pane
|
|
assert "Attachments can't be sent while the assistant is working." in pane
|
|
assert "if (snap.uploading)" in pane
|
|
assert "Wait for attachments to finish uploading before sending." in pane
|
|
assert "!attachments.isEmpty()" in pane or "!this.attachments.isEmpty()" in pane
|
|
assert "loadPasteThresholdChars" not in coordinator
|
|
|
|
for page in (
|
|
_ROOT / "turnstone/ui/static/index.html",
|
|
_ROOT / "turnstone/console/static/index.html",
|
|
_ROOT / "turnstone/console/static/coordinator/index.html",
|
|
):
|
|
body = page.read_text(encoding="utf-8")
|
|
assert "/shared/composer_paste_text.js" in body, page
|
|
assert body.index("/shared/composer_paste_text.js") < body.index("/shared/composer.js"), (
|
|
page
|
|
)
|