Files
ImpTune/.planning/phases/06-wire-icon-intunewin/06-01-PLAN.md
T
2026-04-10 15:41:05 +02:00

171 lines
6.8 KiB
Markdown

---
phase: 06-wire-icon-intunewin
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- imptune/api/packages.py
- tests/test_packages.py
autonomous: true
requirements:
- PKG-04
must_haves:
truths:
- "Exported .intunewin includes icon.png in staging when printer has an uploaded icon"
- "Exported .intunewin succeeds without error when printer has no icon"
artifacts:
- path: "imptune/api/packages.py"
provides: "Icon lookup and copy into tmpdir staging"
contains: "Icon.get_or_none"
- path: "tests/test_packages.py"
provides: "Integration tests for icon-in-package and no-icon baseline"
contains: "test_intunewin_includes_icon"
key_links:
- from: "imptune/api/packages.py"
to: "imptune/db/models.py"
via: "Icon.get_or_none(Icon.printer == printer.id)"
pattern: "Icon\\.get_or_none"
- from: "imptune/api/packages.py"
to: "imptune/config.py"
via: "cfg.ICONS_DIR for icon source path"
pattern: "cfg\\.ICONS_DIR"
---
<objective>
Wire the uploaded PNG icon into the .intunewin export pipeline so that printers with an uploaded icon include it in the deployment package.
Purpose: Closes the PKG-04 gap — icon upload exists (Phase 5) but the .intunewin builder never receives the icon file. This is the last unsatisfied v1 requirement.
Output: Modified packages.py with icon lookup + copy, two new integration tests in test_packages.py.
</objective>
<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-wire-icon-intunewin/06-RESEARCH.md
<interfaces>
<!-- Key types and contracts the executor needs. -->
From imptune/db/models.py:
```python
class Icon(BaseModel):
printer = ForeignKeyField(Printer, unique=True, backref="icons")
sha256 = CharField()
original_filename = CharField()
size_bytes = IntegerField()
uploaded_at = DateTimeField(default=datetime.utcnow)
class Meta:
table_name = "icon"
```
From imptune/config.py:
```python
ICONS_DIR = str(Path(DATA_DIR) / "icons")
```
From imptune/api/packages.py (insertion point — line 146, after driver extraction, before build_intunewin):
```python
# Line 132: with tempfile.TemporaryDirectory(prefix="imptune_") as tmpdir:
# Lines 134-139: write install.ps1, uninstall.ps1, detect.ps1
# Lines 142-145: extract driver ZIP into tmpdir/drivers/
# >>> INSERT ICON COPY HERE <<<
# Line 148: output_path = os.path.join(tmpdir, "out.intunewin")
# Line 149: build_intunewin(tmpdir, "install.ps1", output_path)
```
From tests/test_packages.py (existing fixtures):
```python
@pytest.fixture
def setup_printer_with_driver(tmp_data_dir, driver_zip_bytes):
# Creates Driver + Printer records, writes driver ZIP to cfg.DRIVERS_DIR
# Returns (printer, driver)
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Add icon-in-package tests and wire icon into packages.py</name>
<files>tests/test_packages.py, imptune/api/packages.py</files>
<behavior>
- test_intunewin_includes_icon: upload a 256x256 PNG icon for the printer, monkeypatch build_intunewin to capture staged file list, call GET /printers/{id}/packages/intunewin, assert "icon.png" is in staged files and response is 200
- test_intunewin_without_icon_succeeds: no icon uploaded, monkeypatch build_intunewin, call GET /printers/{id}/packages/intunewin, assert response is 200 (no crash from missing icon)
</behavior>
<action>
RED phase — add two tests to TestIntunewinDownload class in tests/test_packages.py:
1. `test_intunewin_includes_icon(self, client, setup_printer_with_driver, tmp_data_dir, monkeypatch)`:
- Create a 256x256 RGBA PNG using `PIL.Image.new("RGBA", (256, 256), color="red")`
- POST it to `/printers/{printer.id}/icon` as multipart file upload
- Assert upload returns 200
- Define `fake_build(source_dir, setup_file, output_path)` that captures `os.listdir(source_dir)` into a list and writes `b"FAKE"` to output_path
- Monkeypatch `imptune.api.packages.build_intunewin` with fake_build
- GET `/printers/{printer.id}/packages/intunewin`
- Assert status 200 and `"icon.png"` in captured staged files
2. `test_intunewin_without_icon_succeeds(self, client, setup_printer_with_driver, monkeypatch)`:
- Same fake_build monkeypatch (no icon upload)
- GET `/printers/{printer.id}/packages/intunewin`
- Assert status 200
Run tests — both MUST fail (icon.png not staged, and second test should actually pass since no icon code yet — if it passes, that is acceptable for the baseline).
GREEN phase — modify imptune/api/packages.py:
1. Add `import shutil` at top
2. Add `from imptune.db.models import Icon` to the existing models import (line 12 area — add Icon next to Printer)
3. Inside `get_intunewin_package()`, after the driver ZIP extraction block (after line 145) and BEFORE `build_intunewin()` (line 149), insert:
```python
# Copy icon into staging if one exists for this printer
icon_record = Icon.get_or_none(Icon.printer == printer.id)
if icon_record is not None:
icon_src = os.path.join(cfg.ICONS_DIR, icon_record.sha256)
if os.path.isfile(icon_src):
shutil.copy2(icon_src, os.path.join(tmpdir, "icon.png"))
```
This is 4 lines of production code. The icon is optional — missing DB record or missing file on disk both result in silent skip (no error, export proceeds without icon).
Run tests again — both MUST pass.
</action>
<verify>
<automated>python -m pytest tests/test_packages.py -x -q</automated>
</verify>
<done>
- test_intunewin_includes_icon passes: icon.png present in staged files when icon uploaded
- test_intunewin_without_icon_succeeds passes: export works with no icon
- All pre-existing test_packages.py tests still pass (no regressions)
- Full test suite green: python -m pytest tests/ -q
</done>
</task>
</tasks>
<verification>
- `python -m pytest tests/test_packages.py -x -q` — all tests pass including two new icon tests
- `python -m pytest tests/ -q` — full suite green, no regressions
- Manual code review: `shutil.copy2` call is BEFORE `build_intunewin()` call (not after)
- Manual code review: `Icon` import added, `cfg.ICONS_DIR` used (not hardcoded path)
</verification>
<success_criteria>
1. Printers with an uploaded icon have icon.png included in .intunewin staging directory
2. Printers without an icon export successfully with no error
3. All existing tests pass without modification
4. PKG-04 requirement satisfied
</success_criteria>
<output>
After completion, create `.planning/phases/06-wire-icon-intunewin/06-01-SUMMARY.md`
</output>