tenseleyflow/documentlanguagemodel / 41f7631

Browse files

Cover pack edge branches

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
41f7631ad2425dd91ced591047243c590d4c305d
Parents
284b257
Tree
8bd6726

4 changed files

StatusFile+-
M tests/unit/pack/test_integrity.py 8 0
M tests/unit/pack/test_migrations.py 6 0
M tests/unit/pack/test_packer.py 22 0
M tests/unit/pack/test_unpacker.py 103 0
tests/unit/pack/test_integrity.pymodified
@@ -64,6 +64,14 @@ class TestReadChecksums:
6464
         with pytest.raises(PackIntegrityError):
6565
             read_checksums(tmp_path)
6666
 
67
+    def test_blank_lines_are_ignored(self, tmp_path: Path) -> None:
68
+        (tmp_path / "CHECKSUMS.sha256").write_text(
69
+            f"\n{'a' * 64}  a.txt\n\n",
70
+            encoding="utf-8",
71
+        )
72
+
73
+        assert read_checksums(tmp_path) == {"a.txt": "a" * 64}
74
+
6775
     def test_malformed_line_raises(self, tmp_path: Path) -> None:
6876
         (tmp_path / "CHECKSUMS.sha256").write_text("not a valid line\n")
6977
         with pytest.raises(PackIntegrityError):
tests/unit/pack/test_migrations.pymodified
@@ -11,6 +11,7 @@ from dlm.pack.errors import PackFormatVersionError
1111
 from dlm.pack.format import CURRENT_PACK_FORMAT_VERSION
1212
 from dlm.pack.migrations import PACK_MIGRATORS, register
1313
 from dlm.pack.migrations.dispatch import apply_pending
14
+from dlm.pack.migrations.v1 import migrate as migrate_v1
1415
 
1516
 
1617
 @pytest.fixture
@@ -99,3 +100,8 @@ class TestCoverageEnforcement:
99100
             f"expected range [1, {CURRENT_PACK_FORMAT_VERSION}). Register a "
100101
             "migrator under src/dlm/pack/migrations/ when bumping the version."
101102
         )
103
+
104
+
105
+class TestV1IdentityMigrator:
106
+    def test_v1_identity_migrator_returns_same_root(self, tmp_path: Path) -> None:
107
+        assert migrate_v1(tmp_path) == tmp_path
tests/unit/pack/test_packer.pymodified
@@ -58,6 +58,14 @@ def _tar_members(pack_path: Path) -> list[tuple[str, int]]:
5858
             return [(m.name, m.size) for m in tar]
5959
 
6060
 
61
+def _store_root_for(doc: Path) -> Path:
62
+    from dlm.doc.parser import parse_file
63
+    from dlm.store.paths import for_dlm
64
+
65
+    parsed = parse_file(doc)
66
+    return for_dlm(parsed.frontmatter.dlm_id).root
67
+
68
+
6169
 class TestPackShape:
6270
     def test_contains_required_entries(self, tmp_path: Path) -> None:
6371
         doc = _scaffold_doc_and_store(tmp_path)
@@ -83,6 +91,20 @@ class TestPackShape:
8391
         assert result.path == custom
8492
         assert custom.is_file()
8593
 
94
+    def test_default_pack_excludes_exports_and_cache_trees(self, tmp_path: Path) -> None:
95
+        doc = _scaffold_doc_and_store(tmp_path)
96
+        store_root = _store_root_for(doc)
97
+        (store_root / "exports").mkdir(exist_ok=True)
98
+        (store_root / "cache").mkdir(exist_ok=True)
99
+        (store_root / "exports" / "artifact.txt").write_text("export", encoding="utf-8")
100
+        (store_root / "cache" / "base.bin").write_text("base", encoding="utf-8")
101
+
102
+        result = pack(doc)
103
+
104
+        names = {name for name, _size in _tar_members(result.path)}
105
+        assert "store/exports/artifact.txt" not in names
106
+        assert "store/cache/base.bin" not in names
107
+
86108
 
87109
 class TestContentTypeLabel:
88110
     def test_minimal_default(self, tmp_path: Path) -> None:
tests/unit/pack/test_unpacker.pymodified
@@ -10,6 +10,7 @@ from pathlib import Path
1010
 import pytest
1111
 import zstandard as zstd
1212
 
13
+import dlm.pack.unpacker as unpacker_mod
1314
 from dlm.pack.errors import (
1415
     PackFormatVersionError,
1516
     PackIntegrityError,
@@ -97,6 +98,22 @@ class TestHappyPath:
9798
         assert result.dlm_path == tmp_path / "out" / "mydoc.dlm"
9899
         assert result.dlm_path.read_text().startswith("---")
99100
 
101
+    def test_existing_quarantine_is_removed_before_force_install(self, tmp_path: Path) -> None:
102
+        import os
103
+
104
+        pack_path = _synth_pack(tmp_path)
105
+        home = tmp_path / "home"
106
+        unpack(pack_path, home=home, out_dir=tmp_path / "out1")
107
+        target = home / "store" / "01TEST"
108
+        quarantine = target.parent / f".{target.name}.old-{os.getpid()}"
109
+        quarantine.mkdir(parents=True)
110
+        (quarantine / "stale.txt").write_text("stale", encoding="utf-8")
111
+
112
+        unpack(pack_path, home=home, force=True, out_dir=tmp_path / "out2")
113
+
114
+        assert target.exists()
115
+        assert not quarantine.exists()
116
+
100117
 
101118
 class TestVersionGate:
102119
     def test_newer_than_current_refused(self, tmp_path: Path) -> None:
@@ -391,6 +408,92 @@ class TestLayoutGate:
391408
             unpack(out, home=tmp_path / "home")
392409
 
393410
 
411
+class TestReadPackMemberBytes:
412
+    def test_reads_named_member_bytes(self, tmp_path: Path) -> None:
413
+        pack_path = _synth_pack(tmp_path)
414
+
415
+        data = unpacker_mod.read_pack_member_bytes(pack_path, "dlm/mydoc.dlm")
416
+
417
+        assert data is not None
418
+        assert data.startswith(b"---")
419
+
420
+    def test_missing_member_returns_none(self, tmp_path: Path) -> None:
421
+        pack_path = _synth_pack(tmp_path)
422
+
423
+        assert unpacker_mod.read_pack_member_bytes(pack_path, "provenance.json") is None
424
+
425
+    def test_unsafe_member_is_refused(self, tmp_path: Path) -> None:
426
+        payload = tmp_path / "payload.txt"
427
+        payload.write_text("evil", encoding="utf-8")
428
+        out = tmp_path / "unsafe.pack"
429
+        cctx = zstd.ZstdCompressor(level=1)
430
+        with out.open("wb") as fh, cctx.stream_writer(fh) as compressor:
431
+            with tarfile.open(fileobj=compressor, mode="w|") as tar:
432
+                tar.add(payload, arcname="../escape")
433
+
434
+        with pytest.raises(PackLayoutError, match="unsafe tar entry"):
435
+            unpacker_mod.read_pack_member_bytes(out, "../escape")
436
+
437
+    def test_oversized_member_is_refused(
438
+        self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
439
+    ) -> None:
440
+        monkeypatch.setattr(unpacker_mod, "_MAX_TAR_MEMBER_BYTES", 10)
441
+        payload = tmp_path / "big.bin"
442
+        payload.write_bytes(b"x" * 100)
443
+        out = tmp_path / "big.pack"
444
+        cctx = zstd.ZstdCompressor(level=1)
445
+        with out.open("wb") as fh, cctx.stream_writer(fh) as compressor:
446
+            with tarfile.open(fileobj=compressor, mode="w|") as tar:
447
+                tar.add(payload, arcname="big.bin")
448
+
449
+        with pytest.raises(PackLayoutError, match="per-member cap"):
450
+            unpacker_mod.read_pack_member_bytes(out, "big.bin")
451
+
452
+    def test_directory_member_returns_none(self, tmp_path: Path) -> None:
453
+        out = tmp_path / "dir-only.pack"
454
+        cctx = zstd.ZstdCompressor(level=1)
455
+        with out.open("wb") as fh, cctx.stream_writer(fh) as compressor:
456
+            with tarfile.open(fileobj=compressor, mode="w|") as tar:
457
+                info = tarfile.TarInfo(name="folder")
458
+                info.type = tarfile.DIRTYPE
459
+                tar.addfile(info)
460
+
461
+        assert unpacker_mod.read_pack_member_bytes(out, "folder") is None
462
+
463
+
464
+class TestUnpackerInternals:
465
+    def test_is_unsafe_member_rejects_absolute_and_parent_paths(self) -> None:
466
+        assert unpacker_mod._is_unsafe_member("/abs/path") is True
467
+        assert unpacker_mod._is_unsafe_member("\\windows") is True
468
+        assert unpacker_mod._is_unsafe_member("../escape") is True
469
+        assert unpacker_mod._is_unsafe_member("safe/path") is False
470
+
471
+    def test_read_header_malformed_raises(self, tmp_path: Path) -> None:
472
+        (tmp_path / HEADER_FILENAME).write_text("{not json", encoding="utf-8")
473
+
474
+        with pytest.raises(PackLayoutError, match=f"cannot read {HEADER_FILENAME}"):
475
+            unpacker_mod._read_header(tmp_path)
476
+
477
+    def test_read_manifest_malformed_raises(self, tmp_path: Path) -> None:
478
+        (tmp_path / MANIFEST_FILENAME).write_text("{not json", encoding="utf-8")
479
+
480
+        with pytest.raises(PackLayoutError, match=f"cannot read {MANIFEST_FILENAME}"):
481
+            unpacker_mod._read_manifest(tmp_path)
482
+
483
+    def test_find_dlm_file_requires_exactly_one_file(self, tmp_path: Path) -> None:
484
+        dlm_dir = tmp_path / "dlm"
485
+        dlm_dir.mkdir()
486
+
487
+        with pytest.raises(PackLayoutError, match="expected exactly one .dlm file"):
488
+            unpacker_mod._find_dlm_file(dlm_dir)
489
+
490
+        (dlm_dir / "a.dlm").write_text("a", encoding="utf-8")
491
+        (dlm_dir / "b.dlm").write_text("b", encoding="utf-8")
492
+
493
+        with pytest.raises(PackLayoutError, match="expected exactly one .dlm file"):
494
+            unpacker_mod._find_dlm_file(dlm_dir)
495
+
496
+
394497
 class TestForce:
395498
     def test_existing_store_refused_without_force(self, tmp_path: Path) -> None:
396499
         pack_path = _synth_pack(tmp_path)