@@ -60,6 +60,58 @@ class TestModelSpec: |
| 60 | 60 | spec = ModelSpec(base="x") |
| 61 | 61 | assert spec.adapter is None |
| 62 | 62 | |
| 63 | + def test_dtype_enum_accepts_known_values(self) -> None: |
| 64 | + """DC5 — every ``dtype`` branch parses cleanly. Backends rely |
| 65 | + on this validation, not their own — passing ``fp12`` here would |
| 66 | + otherwise crash deep inside the HF loader.""" |
| 67 | + for dtype in ("auto", "fp16", "bf16", "fp32"): |
| 68 | + spec = ModelSpec(base="x", dtype=dtype) # type: ignore[arg-type] |
| 69 | + assert spec.dtype == dtype |
| 70 | + |
| 71 | + def test_dtype_enum_rejects_unknown(self) -> None: |
| 72 | + with pytest.raises(ValidationError): |
| 73 | + ModelSpec(base="x", dtype="fp12") # type: ignore[arg-type] |
| 74 | + |
| 75 | + def test_trust_remote_code_default_false(self) -> None: |
| 76 | + """DC5 — default is False (safe posture); user must opt in.""" |
| 77 | + assert ModelSpec(base="x").trust_remote_code is False |
| 78 | + |
| 79 | + def test_trust_remote_code_accepts_true(self) -> None: |
| 80 | + assert ModelSpec(base="x", trust_remote_code=True).trust_remote_code is True |
| 81 | + |
| 82 | + def test_endpoint_default_none(self) -> None: |
| 83 | + """DC5 — ``endpoint`` is an ``api``-backend-only field. Default |
| 84 | + ``None`` when not specified.""" |
| 85 | + assert ModelSpec(base="x").endpoint is None |
| 86 | + |
| 87 | + def test_endpoint_can_be_set(self) -> None: |
| 88 | + """DC5 — the ``api`` kind expects an endpoint URL.""" |
| 89 | + spec = ModelSpec( |
| 90 | + base="gpt-3.5-turbo-instruct", |
| 91 | + kind="api", |
| 92 | + endpoint="http://localhost:11434", |
| 93 | + ) |
| 94 | + assert spec.endpoint == "http://localhost:11434" |
| 95 | + assert spec.kind == "api" |
| 96 | + |
| 97 | + def test_entry_point_optional_without_custom(self) -> None: |
| 98 | + """DC5 — non-custom kinds don't need an entry_point.""" |
| 99 | + spec = ModelSpec(base="x", kind="hf") |
| 100 | + assert spec.entry_point is None |
| 101 | + |
| 102 | + def test_custom_kind_accepts_entry_point(self) -> None: |
| 103 | + """DC5 — ``custom`` kind stores the entry_point verbatim (the |
| 104 | + runner imports it).""" |
| 105 | + spec = ModelSpec(base="x", kind="custom", entry_point="mypkg.backend:MyBackend") |
| 106 | + assert spec.entry_point == "mypkg.backend:MyBackend" |
| 107 | + |
| 108 | + def test_device_accepts_explicit_cpu(self) -> None: |
| 109 | + """DC5 — ``device`` is a free-form str; ``"auto"`` default |
| 110 | + resolves at backend-load time.""" |
| 111 | + assert ModelSpec(base="x", device="cpu").device == "cpu" |
| 112 | + assert ModelSpec(base="x", device="cuda:0").device == "cuda:0" |
| 113 | + assert ModelSpec(base="x").device == "auto" |
| 114 | + |
| 63 | 115 | |
| 64 | 116 | class TestLoadedModel: |
| 65 | 117 | def test_frozen_dataclass(self) -> None: |