A JSON-backed ticket management system with full CRUD operations, status/priority filtering, prefix-based ID resolution, and test suite.
155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
"""Tests for the tickets package."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tickets.cli import main
|
|
from tickets.models import Priority, Status, Ticket
|
|
from tickets.store import TicketStore
|
|
|
|
|
|
class TestTicketModel:
|
|
def test_create_minimal_ticket(self) -> None:
|
|
t = Ticket(title="Fix login bug")
|
|
assert t.title == "Fix login bug"
|
|
assert t.description == ""
|
|
assert t.priority == Priority.MEDIUM
|
|
assert t.status == Status.TODO
|
|
assert t.id and len(t.id) == 8
|
|
|
|
def test_create_full_ticket(self) -> None:
|
|
t = Ticket(
|
|
title="Deploy v2.0",
|
|
description="Ship the new release",
|
|
priority=Priority.CRITICAL,
|
|
status=Status.IN_PROGRESS,
|
|
assignee="alice",
|
|
tags=["deploy", "urgent"],
|
|
)
|
|
assert t.priority == Priority.CRITICAL
|
|
assert t.status == Status.IN_PROGRESS
|
|
assert t.assignee == "alice"
|
|
assert "urgent" in t.tags
|
|
|
|
def test_roundtrip_dict(self) -> None:
|
|
t = Ticket(title="Test", description="desc", tags=["a", "b"])
|
|
rt = Ticket.from_dict(t.to_dict())
|
|
assert rt.title == t.title
|
|
assert rt.description == t.description
|
|
assert rt.tags == t.tags
|
|
assert rt.priority == t.priority
|
|
|
|
def test_update_fields(self) -> None:
|
|
t = Ticket(title="Old")
|
|
t.update(title="New", status="done", priority="high")
|
|
assert t.title == "New"
|
|
assert t.status == Status.DONE
|
|
assert t.priority == Priority.HIGH
|
|
assert t.updated_at != t.created_at
|
|
|
|
def test_accept_enum_or_string(self) -> None:
|
|
t = Ticket(title="X", priority=Priority.LOW, status=Status.DONE)
|
|
assert t.priority == Priority.LOW
|
|
t.update(priority="critical", status="cancelled")
|
|
assert t.priority == Priority.CRITICAL
|
|
assert t.status == Status.CANCELLED
|
|
|
|
|
|
class TestTicketStore:
|
|
def test_add_and_retrieve(self) -> None:
|
|
with tempfile.TemporaryDirectory() as d:
|
|
fp = Path(d) / "t.json"
|
|
store = TicketStore(fp)
|
|
t = Ticket(title="Hello")
|
|
store.add(t)
|
|
assert t.id in store
|
|
assert store[t.id].title == "Hello"
|
|
|
|
def test_delete(self) -> None:
|
|
with tempfile.TemporaryDirectory() as d:
|
|
fp = Path(d) / "t.json"
|
|
store = TicketStore(fp)
|
|
store.add(Ticket(title="rm me"))
|
|
tid = next(iter(store))
|
|
del store[tid]
|
|
assert tid not in store
|
|
|
|
def test_list_filtering(self) -> None:
|
|
with tempfile.TemporaryDirectory() as d:
|
|
fp = Path(d) / "t.json"
|
|
store = TicketStore(fp)
|
|
store.add(Ticket("a", status=Status.TODO, priority=Priority.LOW))
|
|
store.add(Ticket("b", status=Status.DONE, priority=Priority.HIGH))
|
|
store.add(Ticket("c", status=Status.DONE, priority=Priority.LOW))
|
|
assert len(store.list_tickets(status="done")) == 2
|
|
assert len(store.list_tickets(priority="high")) == 1
|
|
|
|
def test_persists_to_disk(self) -> None:
|
|
with tempfile.TemporaryDirectory() as d:
|
|
fp = Path(d) / "t.json"
|
|
store = TicketStore(fp)
|
|
store.add(Ticket("persist"))
|
|
assert fp.exists()
|
|
# Reload
|
|
store2 = TicketStore(fp)
|
|
assert len(store2) == 1
|
|
|
|
|
|
class TestCLI:
|
|
def test_create_and_view(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
fp = tmp_path / "t.json"
|
|
monkeypatch.setattr("tickets.cli.TicketStore", lambda: TicketStore(fp))
|
|
|
|
main(["create", "My ticket", "-d", "desc", "-p", "high"])
|
|
store = TicketStore(fp)
|
|
assert len(store) == 1
|
|
tid = next(iter(store))
|
|
|
|
main(["view", tid])
|
|
main(["list", "-s", "todo"])
|
|
main(["list"])
|
|
|
|
def test_update(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
fp = tmp_path / "t.json"
|
|
monkeypatch.setattr("tickets.cli.TicketStore", lambda: TicketStore(fp))
|
|
|
|
main(["create", "Old title"])
|
|
store = TicketStore(fp)
|
|
tid = next(iter(store))
|
|
|
|
main(["update", tid, "-t", "New title", "-s", "done"])
|
|
store2 = TicketStore(fp)
|
|
assert store2[tid].title == "New title"
|
|
assert store2[tid].status == Status.DONE
|
|
|
|
def test_delete_with_force(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
fp = tmp_path / "t.json"
|
|
monkeypatch.setattr("tickets.cli.TicketStore", lambda: TicketStore(fp))
|
|
|
|
main(["create", "To delete"])
|
|
store = TicketStore(fp)
|
|
assert len(store) == 1
|
|
tid = next(iter(store))
|
|
|
|
main(["delete", tid, "--force"])
|
|
store2 = TicketStore(fp)
|
|
assert len(store2) == 0
|
|
|
|
def test_prefix_match(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
fp = tmp_path / "t.json"
|
|
monkeypatch.setattr("tickets.cli.TicketStore", lambda: TicketStore(fp))
|
|
|
|
main(["create", "Ticket 1"])
|
|
store = TicketStore(fp)
|
|
tid = next(iter(store))
|
|
prefix = tid[:4]
|
|
|
|
# Should resolve via prefix
|
|
main(["view", prefix])
|
|
|