"""Unit tests: the ApiToken model registers the api_tokens contract (phase 79, task 01). Schema-level assertions without a live DB (the ``tests/unit/test_models.py`` doc_drafts precedent): the table name, the column set + nullability, the UNIQUE ``token_hash`` (two tokens with the same hash must collide — the unique constraint backing ``ix_api_tokens_token_hash``), and a display-only, non-unique ``label``. The real duplicate-rejection behaviour is pinned against the dev DB in ``tests/integration/test_migration_0012.py``. """ from __future__ import annotations from sqlalchemy import PrimaryKeyConstraint, String, UniqueConstraint from sqlalchemy.schema import Table import app.models # noqa: F401 (import registers all tables on Base.metadata) from app.db import Base def _table() -> Table: return Base.metadata.tables["api_tokens"] def test_api_tokens_table_registered() -> None: assert "api_tokens" in Base.metadata.tables, "ApiToken must register api_tokens" assert _table().name == "api_tokens" def test_api_tokens_column_contract() -> None: """The column set + nullability: ``id`` UUID PK; ``label`` / ``token_hash`` NOT NULL; ``created_at`` NOT NULL with a server default (now()); ``last_used_at`` / ``revoked_at`` NULL until the service (tasks 02/03) sets them.""" tok = _table() assert set(tok.c.keys()) == { "id", "label", "token_hash", "created_at", "last_used_at", "revoked_at", } assert tok.c["id"].primary_key is True, "api_tokens.id must be the PK" assert tok.c["id"].nullable is False, "api_tokens.id must be NOT NULL" assert tok.c["label"].nullable is False, "label must be NOT NULL" label_type = tok.c["label"].type assert isinstance(label_type, String), "label must be String(120)" assert label_type.length == 120, "label must be String(120)" assert tok.c["token_hash"].nullable is False, "token_hash must be NOT NULL" hash_type = tok.c["token_hash"].type assert isinstance(hash_type, String), "token_hash must be String(64)" assert hash_type.length == 64, ( "token_hash must be String(64) — a sha256 hex digest" ) assert tok.c["created_at"].nullable is False, "created_at must be NOT NULL" assert tok.c["created_at"].server_default is not None, ( "created_at needs a server default (now())" ) for name in ("last_used_at", "revoked_at"): assert tok.c[name].nullable is True, ( f"{name} must be NULL until first use / revocation" ) def test_api_tokens_token_hash_is_unique() -> None: """Two tokens with the same hash must collide: a UNIQUE constraint covers exactly ``token_hash`` (the backing constraint of the ``ix_api_tokens_token_hash`` unique index — the stored credential is the lookup key).""" tok = _table() uq = [ c for c in tok.constraints if isinstance(c, UniqueConstraint) and not isinstance(c, PrimaryKeyConstraint) and {col.name for col in c.columns} == {"token_hash"} ] assert uq, "api_tokens must be unique on (token_hash) — the stored credential" def test_api_tokens_label_is_not_unique() -> None: """``label`` is the hand-out name — display-only: the column itself is not unique and no unique constraint may cover it (two tokens can share a label, e.g. two "alice" tokens issued at different times).""" tok = _table() assert not tok.c["label"].unique, "label must not be unique" covering = [ c for c in tok.constraints if isinstance(c, UniqueConstraint) and any(col.name == "label" for col in c.columns) ] assert not covering, "no unique constraint may cover api_tokens.label"