|
1
|
"""Tests for boilerworks.manifest.""" |
|
2
|
|
|
3
|
from __future__ import annotations |
|
4
|
|
|
5
|
import pytest |
|
6
|
from pydantic import ValidationError |
|
7
|
|
|
8
|
from boilerworks.manifest import BoilerworksManifest, DataConfig, ServicesConfig |
|
9
|
from boilerworks.manifest import TestingConfig as TestingCfg |
|
10
|
|
|
11
|
|
|
12
|
def _make_manifest(**kwargs) -> BoilerworksManifest: |
|
13
|
defaults = { |
|
14
|
"project": "my-app", |
|
15
|
"family": "django-nextjs", |
|
16
|
"size": "full", |
|
17
|
} |
|
18
|
defaults.update(kwargs) |
|
19
|
return BoilerworksManifest(**defaults) |
|
20
|
|
|
21
|
|
|
22
|
class TestValidManifest: |
|
23
|
def test_minimal_manifest_passes(self) -> None: |
|
24
|
m = _make_manifest() |
|
25
|
assert m.project == "my-app" |
|
26
|
assert m.family == "django-nextjs" |
|
27
|
assert m.size == "full" |
|
28
|
|
|
29
|
def test_defaults_are_correct(self) -> None: |
|
30
|
m = _make_manifest() |
|
31
|
assert m.topology == "standard" |
|
32
|
assert m.cloud is None |
|
33
|
assert m.region is None |
|
34
|
assert m.domain is None |
|
35
|
assert m.mobile is False |
|
36
|
assert m.web_presence is False |
|
37
|
assert m.compliance == [] |
|
38
|
assert m.template_versions == {} |
|
39
|
|
|
40
|
def test_full_manifest_passes(self, valid_manifest: BoilerworksManifest) -> None: |
|
41
|
assert valid_manifest.project == "my-app" |
|
42
|
assert valid_manifest.cloud == "aws" |
|
43
|
assert valid_manifest.region == "us-east-1" |
|
44
|
|
|
45
|
def test_micro_template(self) -> None: |
|
46
|
m = _make_manifest(family="fastapi-micro", size="micro") |
|
47
|
assert m.size == "micro" |
|
48
|
|
|
49
|
def test_edge_template(self) -> None: |
|
50
|
m = _make_manifest(family="astro-site", size="edge") |
|
51
|
assert m.size == "edge" |
|
52
|
|
|
53
|
|
|
54
|
class TestProjectSlugValidation: |
|
55
|
def test_valid_slugs(self) -> None: |
|
56
|
for slug in ["my-app", "app", "my-app-v2", "a1b2c3"]: |
|
57
|
m = _make_manifest(project=slug) |
|
58
|
assert m.project == slug |
|
59
|
|
|
60
|
def test_spaces_fail(self) -> None: |
|
61
|
with pytest.raises(ValidationError, match="project name"): |
|
62
|
_make_manifest(project="my app") |
|
63
|
|
|
64
|
def test_uppercase_fails(self) -> None: |
|
65
|
with pytest.raises(ValidationError, match="project name"): |
|
66
|
_make_manifest(project="MyApp") |
|
67
|
|
|
68
|
def test_leading_digit_fails(self) -> None: |
|
69
|
with pytest.raises(ValidationError, match="project name"): |
|
70
|
_make_manifest(project="1app") |
|
71
|
|
|
72
|
def test_underscore_fails(self) -> None: |
|
73
|
with pytest.raises(ValidationError, match="project name"): |
|
74
|
_make_manifest(project="my_app") |
|
75
|
|
|
76
|
def test_empty_fails(self) -> None: |
|
77
|
with pytest.raises(ValidationError): |
|
78
|
_make_manifest(project="") |
|
79
|
|
|
80
|
|
|
81
|
class TestFamilyValidation: |
|
82
|
def test_unknown_family_fails(self) -> None: |
|
83
|
with pytest.raises(ValidationError, match="unknown template family"): |
|
84
|
_make_manifest(family="not-a-real-template") |
|
85
|
|
|
86
|
def test_all_known_families_pass(self) -> None: |
|
87
|
from boilerworks.registry import Registry |
|
88
|
|
|
89
|
registry = Registry() |
|
90
|
for t in registry.list_all(): |
|
91
|
m = _make_manifest(family=t.name, size=t.size) |
|
92
|
assert m.family == t.name |
|
93
|
|
|
94
|
|
|
95
|
class TestYamlRoundtrip: |
|
96
|
def test_to_yaml_and_back(self, valid_manifest: BoilerworksManifest) -> None: |
|
97
|
yaml_str = valid_manifest.to_yaml() |
|
98
|
assert "my-app" in yaml_str |
|
99
|
assert "django-nextjs" in yaml_str |
|
100
|
|
|
101
|
restored = BoilerworksManifest.from_yaml(yaml_str) |
|
102
|
assert restored.project == valid_manifest.project |
|
103
|
assert restored.family == valid_manifest.family |
|
104
|
assert restored.size == valid_manifest.size |
|
105
|
assert restored.cloud == valid_manifest.cloud |
|
106
|
assert restored.region == valid_manifest.region |
|
107
|
|
|
108
|
def test_to_yaml_is_valid_yaml(self, valid_manifest: BoilerworksManifest) -> None: |
|
109
|
import yaml |
|
110
|
|
|
111
|
yaml_str = valid_manifest.to_yaml() |
|
112
|
parsed = yaml.safe_load(yaml_str) |
|
113
|
assert isinstance(parsed, dict) |
|
114
|
assert parsed["project"] == "my-app" |
|
115
|
|
|
116
|
def test_from_file(self, valid_manifest: BoilerworksManifest, tmp_path) -> None: |
|
117
|
yaml_file = tmp_path / "boilerworks.yaml" |
|
118
|
valid_manifest.to_file(yaml_file) |
|
119
|
|
|
120
|
loaded = BoilerworksManifest.from_file(yaml_file) |
|
121
|
assert loaded.project == valid_manifest.project |
|
122
|
assert loaded.family == valid_manifest.family |
|
123
|
|
|
124
|
|
|
125
|
class TestNestedModels: |
|
126
|
def test_services_config_defaults(self) -> None: |
|
127
|
s = ServicesConfig() |
|
128
|
assert s.email is None |
|
129
|
assert s.cache == "redis" |
|
130
|
|
|
131
|
def test_data_config_defaults(self) -> None: |
|
132
|
d = DataConfig() |
|
133
|
assert d.database == "postgres" |
|
134
|
assert d.migrations is True |
|
135
|
assert d.seed_data is True |
|
136
|
|
|
137
|
def test_testing_config_defaults(self) -> None: |
|
138
|
t = TestingCfg() |
|
139
|
assert t.e2e is None |
|
140
|
assert t.unit is True |
|
141
|
assert t.integration is True |
|
142
|
|
|
143
|
def test_services_email_options(self) -> None: |
|
144
|
for provider in ("ses", "sendgrid", "mailgun"): |
|
145
|
s = ServicesConfig(email=provider) |
|
146
|
assert s.email == provider |
|
147
|
|