Skip to content

Commit c0a5865

Browse files
Lukas Geigerclaude
andcommitted
ci: add Linux/macOS source-platform smoke tests
- tests/source_platform_smoke.py: 8 headless tests covering version, ops (rename/move/delete), models (parse_txt_line, Queue roundtrip), paths.data_dir, and worker.run_once — no GUI or cloud client needed - .github/workflows/source-platform-smoke.yml: CI matrix for ubuntu-latest and macos-latest on every push/PR to main - PORTIERUNGSPLAN.md: mark core-logic testability as done, document smoke-test baseline (2026-06-10) - CHANGELOG.md: record new tests and workflow under [Unreleased] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 51b4344 commit c0a5865

4 files changed

Lines changed: 140 additions & 1 deletion

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Source Platform Smoke
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
smoke:
11+
name: Smoke – ${{ matrix.os }}
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-latest, macos-latest]
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Python 3.11
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.11"
25+
26+
- name: Install pytest
27+
run: pip install pytest
28+
29+
- name: Run source platform smoke tests
30+
env:
31+
PYTHONPATH: src
32+
PYTHONIOENCODING: utf-8
33+
run: python -m pytest tests/source_platform_smoke.py -v

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
55

66
## [Unreleased]
77

8+
### Hinzugefügt / Added
9+
- `tests/source_platform_smoke.py`: headless Smoke-Tests für Linux und macOS — prüft Modul-Import, Version, `ops`-Operationen (rename/move/delete), `models.Queue`-Persistenz, `paths.data_dir()` und `worker.run_once()` ohne Cloud-Client oder GUI.
10+
- `.github/workflows/source-platform-smoke.yml`: CI-Matrix für `ubuntu-latest` und `macos-latest`, die die Smoke-Tests bei jedem Push/PR auf `main` ausführt.
11+
812
### Behoben / Fixed
913
- Guard in `_refresh_status()` against race between queue reload and worker thread.
1014
- Thread-safe dict snapshot in `_watcher_tick` (tray).

PORTIERUNGSPLAN.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ CI/CD: GitHub Actions Matrix-Build (windows-latest, ubuntu-latest, macos-latest)
146146

147147
## Testbarkeit
148148

149-
- Kernlogik (ops, models, worker, watcher): Bereits plattformneutral, Tests laufen überall
149+
- Kernlogik (ops, models, worker, watcher): Bereits plattformneutral, Tests laufen überall
150150
- Provider-Tests: Komplett gemockt (kein echtes tasklist/pgrep nötig)
151151
- Autostart/Kontextmenü: Integration-Tests nur auf Zielplattform, Unit-Tests gemockt
152+
153+
### Source-Platform Smoke-Tests (CI aktiv)
154+
155+
`tests/source_platform_smoke.py` + `.github/workflows/source-platform-smoke.yml` prüfen auf
156+
`ubuntu-latest` und `macos-latest` headless: Import, Version, ops, Queue, paths, worker.
157+
Kein Cloud-Client, kein GUI, kein pip-Extra (nur pytest). Stand: 2026-06-10.

tests/source_platform_smoke.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Source-Platform Smoke-Test für CloudLockFixer.
2+
3+
Prüft auf Linux und macOS (und Windows) ohne Cloud-Sync-Client oder GUI:
4+
- Modul-Import und Version
5+
- ops: rename, move, delete (plattformneutral, stdlib-only)
6+
- models: parse_txt_line, Queue-Persistenz
7+
- paths: data_dir cross-platform
8+
- worker: run_once ohne Cloud-Provider
9+
"""
10+
from __future__ import annotations
11+
12+
from pathlib import Path
13+
14+
15+
def test_version_defined():
16+
import cloudlockfixer
17+
assert cloudlockfixer.__version__
18+
assert "." in cloudlockfixer.__version__
19+
20+
21+
def test_ops_delete_file(tmp_path):
22+
from cloudlockfixer.ops import _delete_path
23+
f = tmp_path / "target.txt"
24+
f.write_text("x", encoding="utf-8")
25+
ok, msg = _delete_path(f)
26+
assert ok, msg
27+
assert not f.exists()
28+
29+
30+
def test_ops_rename_directory(tmp_path):
31+
from cloudlockfixer.ops import rename_path
32+
src = tmp_path / "old_dir"
33+
src.mkdir()
34+
(src / "file.txt").write_text("content", encoding="utf-8")
35+
ok, msg = rename_path(src, "new_dir")
36+
assert ok, msg
37+
assert (tmp_path / "new_dir" / "file.txt").exists()
38+
assert not src.exists()
39+
40+
41+
def test_ops_move_path(tmp_path):
42+
from cloudlockfixer.ops import move_path
43+
src = tmp_path / "a" / "item"
44+
src.mkdir(parents=True)
45+
(src / "data.txt").write_text("payload", encoding="utf-8")
46+
dst = tmp_path / "b" / "item"
47+
ok, msg = move_path(src, dst)
48+
assert ok, msg
49+
assert (dst / "data.txt").read_text(encoding="utf-8") == "payload"
50+
assert not src.exists()
51+
52+
53+
def test_parse_txt_line_variants():
54+
from cloudlockfixer.models import parse_txt_line
55+
assert parse_txt_line("# Kommentar") is None
56+
assert parse_txt_line(" ") is None
57+
t = parse_txt_line('delete "/tmp/testfile"')
58+
assert t is not None
59+
assert t.chain[0].op == "delete"
60+
assert t.chain[0].src == "/tmp/testfile"
61+
t2 = parse_txt_line('rename "/tmp/old" "new_name"')
62+
assert t2.chain[0].op == "rename"
63+
assert t2.chain[0].arg == "new_name"
64+
65+
66+
def test_queue_add_and_roundtrip(tmp_path):
67+
from cloudlockfixer.models import Queue, Step, Task
68+
q = Queue(tmp_path)
69+
task = Task(chain=[Step(op="delete", src="/tmp/smoke_test_x")])
70+
q.add(task)
71+
q2 = Queue(tmp_path)
72+
assert len(q2.tasks) == 1
73+
assert q2.tasks[0].chain[0].op == "delete"
74+
assert q2.tasks[0].id == task.id
75+
76+
77+
def test_paths_data_dir_cross_platform():
78+
from cloudlockfixer import paths
79+
d = paths.data_dir()
80+
assert isinstance(d, Path)
81+
# Linux/macOS: LOCALAPPDATA fehlt -> ~/.cloudlockfixer
82+
# Windows: %LOCALAPPDATA%\CloudLockFixer
83+
assert d.exists()
84+
85+
86+
def test_worker_run_once_local(tmp_path):
87+
from cloudlockfixer.models import Queue, Step, Task
88+
from cloudlockfixer.worker import run_once
89+
q = Queue(tmp_path)
90+
src = tmp_path / "source.txt"
91+
src.write_text("smoke", encoding="utf-8")
92+
q.add(Task(chain=[Step(op="rename", src=str(src), arg="renamed.txt")]))
93+
summary = run_once(q, force_pause=False)
94+
assert summary["done"] == 1
95+
assert (tmp_path / "renamed.txt").exists()
96+
assert not src.exists()

0 commit comments

Comments
 (0)