Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions backend/tests/test_public_demo_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,49 @@ def test_generator_is_reproducible_and_contains_searchable_source(self):
self.assertIn("src/catalog.py", paths)
self.assertIn("src/api.py", paths)
self.assertIn("find_title", archive.read("src/catalog.py").decode("utf-8"))
self.assertTrue(all(
member.date_time == (1980, 1, 1, 0, 0, 0)
and member.create_system == 3
and member.external_attr >> 16 == 0o100644
for member in archive.infolist()
))
self.assertTrue(
all(not p.startswith("/") and ".." not in Path(p).parts for p in paths)
)

def test_generator_does_not_overwrite_existing_output(self):
with tempfile.TemporaryDirectory() as directory:
destination = Path(directory) / "existing.zip"
sentinel = b"EXISTING_DOCUMENT_DO_NOT_TOUCH"
destination.write_bytes(sentinel)
result = subprocess.run(
[sys.executable, str(GENERATOR), "--output", str(destination)],
cwd=ROOT, capture_output=True, text=True, timeout=20
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("already exists", result.stderr.lower())
self.assertEqual(destination.read_bytes(), sentinel)


@unittest.skipIf(sys.platform == "win32", "Windows symlink setup needs special privileges")
def test_generator_does_not_follow_existing_symlink(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "important.txt"
sentinel = b"DO_NOT_REPLACE_SYMLINK_TARGET"
target.write_bytes(sentinel)
link = root / "fictional-catalog.zip"
link.symlink_to(target)
result = subprocess.run(
[sys.executable, str(GENERATOR), "--output", str(link)],
cwd=ROOT, capture_output=True, text=True, timeout=20
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("already exists", result.stderr.lower())
self.assertTrue(link.is_symlink())
self.assertEqual(target.read_bytes(), sentinel)



class PublishedDemoHttpTests(unittest.IsolatedAsyncioTestCase):
async def test_published_zip_imports_searches_and_deletes_without_ollama(self):
Expand Down
2 changes: 2 additions & 0 deletions docs/first-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Import **fictional-catalog.zip** through the UI, then search for **find_title**.
The search result should show **src/catalog.py**, line **2**. These are real
source lines from a fictional fixture, **not** an AI model answer.

The generator refuses to overwrite an existing output path, including a symlink. If you run the command again, choose a different filename or manually remove only your disposable sample ZIP first.

CI checks this exact public ZIP generator and its real FastAPI ZIP-import/search/
deletion journey, without starting Ollama:

Expand Down
20 changes: 16 additions & 4 deletions examples/create_demo_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,25 @@
def create_demo_zip(destination: Path) -> Path:
destination = Path(destination)
destination.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for path, contents in DEMO_FILES.items():
archive.writestr(path, contents)
# Exclusive creation refuses existing files and symlinks; a first-run demo
# should never replace another file at a caller-selected output path.
with destination.open("xb") as output:
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for path, contents in DEMO_FILES.items():
# An explicit DOS timestamp and file mode keep ZIP members
# reproducible across seconds and independent of host OS.
member = zipfile.ZipInfo(path, date_time=(1980, 1, 1, 0, 0, 0))
member.compress_type = zipfile.ZIP_DEFLATED
member.create_system = 3
member.external_attr = 0o100644 << 16
archive.writestr(member, contents)
return destination

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
print(create_demo_zip(args.output))
try:
print(create_demo_zip(args.output))
except FileExistsError:
parser.error(f"Output already exists: {args.output}. Choose another --output path.")
Loading