"""Scope file reader + materializer tests.""" from __future__ import annotations import json from cloud_sync.scope import ( DEFAULT_EXCLUDE, DEFAULT_INCLUDE, Scope, load, materialize_for_restic, ) def test_load_missing_returns_defaults(tmp_path): s = load(tmp_path) assert s.include == DEFAULT_INCLUDE assert s.exclude == DEFAULT_EXCLUDE def test_load_valid_overrides_defaults(tmp_path): state = tmp_path / ".cloud-sync" state.mkdir() (state / "scope.json").write_text(json.dumps({ "include": ["foo/", "bar.txt"], "exclude": ["**/*.log"], })) s = load(tmp_path) assert s.include == ["foo/", "bar.txt"] assert s.exclude == ["**/*.log"] def test_load_partial_keeps_defaults_for_missing(tmp_path): state = tmp_path / ".cloud-sync" state.mkdir() (state / "scope.json").write_text(json.dumps({"include": ["just-this"]})) s = load(tmp_path) assert s.include == ["just-this"] assert s.exclude == DEFAULT_EXCLUDE def test_load_invalid_falls_back(tmp_path, capsys): state = tmp_path / ".cloud-sync" state.mkdir() (state / "scope.json").write_text("{not valid json") s = load(tmp_path) assert s.include == DEFAULT_INCLUDE captured = capsys.readouterr() assert "invalid" in captured.err.lower() def test_materialize_writes_files(tmp_path): scope = Scope(include=["config/", "options.txt"], exclude=["**/*.log"]) files_from, exclude_from = materialize_for_restic(tmp_path, scope) assert files_from.exists() assert exclude_from.exists() body_in = files_from.read_text().splitlines() body_ex = exclude_from.read_text().splitlines() # trailing slash stripped on include entries assert "config" in body_in assert "options.txt" in body_in assert "**/*.log" in body_ex def test_materialize_creates_state_dir(tmp_path): scope = Scope(include=["x"], exclude=["y"]) files_from, _ = materialize_for_restic(tmp_path, scope) assert files_from.parent.name == ".cloud-sync" assert files_from.parent.exists()