mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:47:02 +01:00
New package. Walks every thread on 4chan's /wsg/, downloads each video it has
not seen, and reviews them one at a time: mpv embedded in a Tk window via --wid
with Keep/Pass/Undo directly beneath the picture.
Dedupe is structural rather than a convention. Every post carrying a file also
carries md5 -- base64 MD5 of the whole file -- so it is the sqlite primary key.
An already-seen video costs zero bytes to skip and a byte-identical repost in
another thread collapses onto the existing row. Verified against a live file:
the API's Nz9OEKdMuMZEdYE6eLTKmA== matched base64(md5(bytes)) exactly.
Undo is unbounded and survives quitting: the verdict trail lives in the index,
not in memory, so yesterday's decisions can still be taken back.
No downloaded video is ever deleted. A pass moves the file to trash/ and leaves
it there; files.apply_move refuses to overwrite an existing destination, so the
guarantee holds by construction rather than per caller.
Three bugs only running it could find, each caught from evidence rather than
guesswork:
- the video stayed black: a <Configure> handler set mpv's geometry, fighting
the automatic sizing --wid already does
- playback never advanced: refresh() ran every tick and re-sent loadfile ~7x
a second, restarting the video continuously
- it froze on one video around the 26th while filenames kept advancing: mpv
pushes an event stream down the same socket and nothing drained it, so the
buffer filled and mpv stopped reading commands. Isolated to command #35
without a drain thread; all 60 honoured with one.
A five-lens review then found nine more, all reproduced before fixing: a failed
mpv start stranded the non-daemon worker forever; undo could clobber a file in
incoming/; a NUL-bearing md5 poisoned the index permanently; an oversized int
killed the worker with no event; gzip bombs on both hosts were accepted; the
board endpoints were fetched unconditionally forever; and Session.missing could
corrupt the undo trail.
mpv runs as a subprocess over its JSON IPC socket rather than via python-mpv,
which resolves libmpv at import time and would fail on CI while passing here.
Third-party surface is requests alone, already declared.
277 package tests, 100% branch coverage on all 22 modules, no suppressions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
"""Tests for verdict planning and the one module that moves files."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from python_pkg.wsg_grabber import files, verdict
|
|
from python_pkg.wsg_grabber.models import FileMove, ReviewItem, Verdict
|
|
from python_pkg.wsg_grabber.states import FileState
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def _item(path: Path) -> ReviewItem:
|
|
"""Build a ReviewItem pointing at *path*.
|
|
|
|
Args:
|
|
path: Location of the video.
|
|
|
|
Returns:
|
|
ReviewItem: Fixture value.
|
|
"""
|
|
return ReviewItem(
|
|
md5="md5",
|
|
path=path,
|
|
orig_name="clip.webm",
|
|
fsize=10,
|
|
width=4,
|
|
height=3,
|
|
)
|
|
|
|
|
|
def test_verdicts_map_to_terminal_states() -> None:
|
|
assert verdict.target_state(Verdict.KEEP) is FileState.KEPT
|
|
assert verdict.target_state(Verdict.SKIP) is FileState.PASSED
|
|
|
|
|
|
def test_verdicts_map_to_directories(tmp_path: Path) -> None:
|
|
keep, trash = tmp_path / "keep", tmp_path / "trash"
|
|
assert verdict.target_dir(Verdict.KEEP, keep, trash) == keep
|
|
assert verdict.target_dir(Verdict.SKIP, keep, trash) == trash
|
|
|
|
|
|
def test_unique_destination_passes_through_when_free(tmp_path: Path) -> None:
|
|
assert verdict.unique_destination(tmp_path, "a.webm", set()) == tmp_path / "a.webm"
|
|
|
|
|
|
def test_unique_destination_suffixes_a_collision(tmp_path: Path) -> None:
|
|
result = verdict.unique_destination(tmp_path, "a.webm", {"a.webm"})
|
|
assert result == tmp_path / "a-2.webm"
|
|
|
|
|
|
def test_unique_destination_keeps_counting(tmp_path: Path) -> None:
|
|
taken = {"a.webm", "a-2.webm", "a-3.webm"}
|
|
assert (
|
|
verdict.unique_destination(tmp_path, "a.webm", taken) == tmp_path / "a-4.webm"
|
|
)
|
|
|
|
|
|
def test_unique_destination_handles_a_name_without_an_extension(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
assert verdict.unique_destination(tmp_path, "a", {"a"}) == tmp_path / "a-2"
|
|
|
|
|
|
def test_plan_move_sends_a_keep_to_the_keep_directory(tmp_path: Path) -> None:
|
|
keep, trash = tmp_path / "keep", tmp_path / "trash"
|
|
move = verdict.plan_move(
|
|
_item(tmp_path / "in" / "x.webm"), Verdict.KEEP, keep, trash
|
|
)
|
|
assert move.dst == keep / "x.webm"
|
|
assert move.src == tmp_path / "in" / "x.webm"
|
|
assert move.md5 == "md5"
|
|
|
|
|
|
def test_plan_move_sends_a_pass_to_the_trash_directory(tmp_path: Path) -> None:
|
|
keep, trash = tmp_path / "keep", tmp_path / "trash"
|
|
move = verdict.plan_move(
|
|
_item(tmp_path / "in" / "x.webm"), Verdict.SKIP, keep, trash
|
|
)
|
|
assert move.dst == trash / "x.webm"
|
|
|
|
|
|
def test_apply_move_relocates_rather_than_deleting(tmp_path: Path) -> None:
|
|
"""A pass must never destroy the file; trash is a destination, not a bin."""
|
|
source = tmp_path / "in" / "x.webm"
|
|
source.parent.mkdir()
|
|
source.write_bytes(b"data")
|
|
move = FileMove(md5="md5", src=source, dst=tmp_path / "trash" / "x.webm")
|
|
|
|
assert files.apply_move(move)
|
|
assert not source.exists()
|
|
assert move.dst.read_bytes() == b"data"
|
|
|
|
|
|
def test_apply_move_reports_a_source_that_has_vanished(tmp_path: Path) -> None:
|
|
move = FileMove(md5="md5", src=tmp_path / "gone.webm", dst=tmp_path / "t.webm")
|
|
assert not files.apply_move(move)
|
|
|
|
|
|
def test_existing_names_lists_the_directory(tmp_path: Path) -> None:
|
|
# a subdirectory, because the isolation fixture puts a fake home in tmp_path
|
|
keep = tmp_path / "keep"
|
|
keep.mkdir()
|
|
(keep / "a.webm").write_bytes(b"")
|
|
(keep / "b.webm").write_bytes(b"")
|
|
assert files.existing_names(keep) == {"a.webm", "b.webm"}
|
|
|
|
|
|
def test_existing_names_of_a_missing_directory_is_empty(tmp_path: Path) -> None:
|
|
assert files.existing_names(tmp_path / "nope") == set()
|
|
|
|
|
|
def test_apply_move_refuses_to_clobber_the_destination(tmp_path: Path) -> None:
|
|
"""The structural backstop: no move may ever destroy an existing file."""
|
|
source = tmp_path / "in" / "x.webm"
|
|
source.parent.mkdir()
|
|
source.write_bytes(b"new")
|
|
victim = tmp_path / "keep" / "x.webm"
|
|
victim.parent.mkdir()
|
|
victim.write_bytes(b"ALREADY HERE")
|
|
|
|
assert not files.apply_move(FileMove(md5="m", src=source, dst=victim))
|
|
assert victim.read_bytes() == b"ALREADY HERE"
|
|
assert source.read_bytes() == b"new"
|