Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-script-generation | 02 | execute | 2 |
|
|
true |
|
|
Purpose: Completes the script generation phase by adding the two simpler templates and exposing all scripts via GET endpoints that the printer detail page (Phase 3) can link to.
Output: uninstall.ps1.j2, detect.ps1.j2, scripts.py router, updated main.py, passing integration tests.
<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>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-script-generation/04-RESEARCH.md @.planning/phases/04-script-generation/04-01-SUMMARY.mdFrom imptune/generators/script_generator.py (created in 04-01):
# Jinja2 Environment already configured with FileSystemLoader for templates/scripts/
# _duplex_map already defined
def render_install(printer_name, ip_address, port_name, driver_name, inf_filename,
duplex_mode, color_mode, paper_size, collate) -> str: ...
# Plan 02 adds: render_uninstall(), render_detect()
From imptune/db/models.py:
class Printer(BaseModel):
name = CharField()
ip_address = CharField()
port_name = CharField()
driver = ForeignKeyField(Driver, null=True, backref="printers")
duplex_mode = CharField(default="OneSided")
color_mode = BooleanField(default=True)
paper_size = CharField(default="A4")
collate = BooleanField(default=True)
class Driver(BaseModel):
driver_desc = CharField(null=True) # JSON list
inf_filename = CharField(null=True)
From imptune/main.py (router registration pattern):
from imptune.api import clients, drivers, health, pages, printers
app.include_router(health.router)
app.include_router(pages.router)
app.include_router(drivers.router)
app.include_router(printers.router)
app.include_router(clients.router)
From tests/conftest.py:
@pytest.fixture
def client(tmp_data_dir):
from imptune.main import app
with TestClient(app) as c:
yield c
@pytest.fixture
def tmp_data_dir(tmp_path, monkeypatch): ...
- `test_render_uninstall`: call render_uninstall("Test Printer", "HP Driver", "IP_10.0.0.1"), assert output contains `Remove-Printer -Name "Test Printer"`, `Remove-PrinterDriver -Name "HP Driver"`, `Remove-PrinterPort -Name "IP_10.0.0.1"`, and `-ErrorAction SilentlyContinue` on all three. Assert Remove-Printer appears BEFORE Remove-PrinterDriver (order matters — driver removal fails if printer still references it).
- `test_render_detect`: call render_detect("Test Printer"), assert output contains `Get-Printer -Name "Test Printer"`, `Write-Output`, `exit 0`, `exit 1`.
Run tests — both MUST fail.
**Implement:**
Add `render_uninstall(printer_name, driver_name, port_name) -> str` to `script_generator.py`:
- Gets `uninstall.ps1.j2` template, renders with the three names.
Add `render_detect(printer_name) -> str` to `script_generator.py`:
- Gets `detect.ps1.j2` template, renders with printer_name.
Create `imptune/templates/scripts/uninstall.ps1.j2`:
```
# Header: Generated by ImpTune — Uninstall script for {{ printer_name }}
Remove-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue
Remove-PrinterDriver -Name "{{ driver_name }}" -ErrorAction SilentlyContinue
Remove-PrinterPort -Name "{{ port_name }}" -ErrorAction SilentlyContinue
```
Create `imptune/templates/scripts/detect.ps1.j2`:
```
# Header: Generated by ImpTune — Detection script for {{ printer_name }}
$printer = Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue
if ($printer) {
Write-Output "Installed: {{ printer_name }}"
exit 0
} else {
exit 1
}
```
Run tests — both MUST pass.
Update `imptune/main.py`:
- Add `scripts` to import: `from imptune.api import clients, drivers, health, pages, printers, scripts`
- Add `app.include_router(scripts.router)` after existing router registrations
Add integration tests to `tests/test_script_generator.py`:
- `test_install_endpoint`: create Driver + Printer via ORM in test, GET `/printers/{id}/scripts/install`, assert 200 + content contains `pnputil`
- `test_uninstall_endpoint`: same setup, GET `/printers/{id}/scripts/uninstall`, assert 200 + `Remove-Printer`
- `test_detect_endpoint`: same setup, GET `/printers/{id}/scripts/detect`, assert 200 + `Write-Output`
- `test_script_endpoint_missing_printer`: GET `/printers/9999/scripts/install`, assert 404
- `test_script_endpoint_no_driver`: create Printer without driver FK, GET install, assert 422
For integration tests, use the `client` fixture from conftest.py. Create test data via ORM:
```python
from imptune.db.models import Driver, Printer
driver = Driver.create(sha256="abc123", original_filename="test.zip", size_bytes=100,
driver_desc='["Test Driver"]', inf_filename="test.inf")
printer = Printer.create(name="Test Printer", ip_address="10.0.0.1", port_name="IP_10.0.0.1",
driver=driver, duplex_mode="LongEdge", color_mode=True,
paper_size="A4", collate=True)
```
<success_criteria>
- render_uninstall produces script with correct removal order
- render_detect follows Intune detection contract (Write-Output + exit 0/1)
- All three script types downloadable via GET /printers/{id}/scripts/{type}
- Error handling: 404 for missing printer, 422 for missing driver/INF
- Scripts router registered in main.py
- Full test suite green </success_criteria>