Skip to content

Commit ea409fe

Browse files
Use PostgreSQL 14 in docker-compose (#644)
* Bring docker-compose PostgreSQL in line with production, tweak broken migration script * Remove postgresql_nulls_not_distinct=True * Remove nullable from ArtefactMatchingRule * wip: fix failing tests * chore: update schema --------- Co-authored-by: Raul Almeida <raul.almeida@canonical.com>
1 parent 1e5f924 commit ea409fe

File tree

11 files changed

+81
-156
lines changed

11 files changed

+81
-156
lines changed

backend/migrations/versions/2026_02_09_1734-e9ada0b8b7a4_migrate_reviewer_families_to_artefact_matching_rules.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ def upgrade() -> None:
8383
"""
8484
SELECT id FROM artefact_matching_rule
8585
WHERE family = :family
86-
AND stage IS NULL
87-
AND track IS NULL
88-
AND branch IS NULL
86+
AND stage = ''
87+
AND track = ''
88+
AND branch = ''
8989
"""
9090
),
9191
{"family": family}
@@ -99,7 +99,7 @@ def upgrade() -> None:
9999
sa.text(
100100
"""
101101
INSERT INTO artefact_matching_rule (family, stage, track, branch, created_at, updated_at)
102-
VALUES (:family, NULL, NULL, NULL, NOW(), NOW())
102+
VALUES (:family, '', '', '', NOW(), NOW())
103103
RETURNING id
104104
"""
105105
),

backend/migrations/versions/2026_02_27_1246-40b42f906881_add_artefact_matching_rules.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ def upgrade() -> None:
3939
# ### commands auto generated by Alembic - please adjust! ###
4040
op.create_table('artefact_matching_rule',
4141
sa.Column('family', family_type, nullable=False),
42-
sa.Column('stage', sa.String(length=100), nullable=True),
43-
sa.Column('track', sa.String(length=200), nullable=True),
44-
sa.Column('branch', sa.String(length=200), nullable=True),
42+
sa.Column('stage', sa.String(length=100), nullable=False, server_default=''),
43+
sa.Column('track', sa.String(length=200), nullable=False, server_default=''),
44+
sa.Column('branch', sa.String(length=200), nullable=False, server_default=''),
4545
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
4646
sa.Column('created_at', sa.DateTime(), nullable=False),
4747
sa.Column('updated_at', sa.DateTime(), nullable=False),
4848
sa.PrimaryKeyConstraint('id', name=op.f('artefact_matching_rule_pkey')),
49-
sa.UniqueConstraint('family', 'stage', 'track', 'branch', name=op.f('artefact_matching_rule_family_stage_track_branch_key'), postgresql_nulls_not_distinct=True)
49+
sa.UniqueConstraint('family', 'stage', 'track', 'branch', name=op.f('artefact_matching_rule_family_stage_track_branch_key'))
5050
)
5151
op.create_table('artefact_matching_rule_team_association',
5252
sa.Column('artefact_matching_rule_id', sa.Integer(), nullable=False),

backend/schemata/openapi.json

Lines changed: 24 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4609,37 +4609,19 @@
46094609
"$ref": "#/components/schemas/FamilyName"
46104610
},
46114611
"stage": {
4612-
"anyOf": [
4613-
{
4614-
"type": "string"
4615-
},
4616-
{
4617-
"type": "null"
4618-
}
4619-
],
4620-
"title": "Stage"
4612+
"type": "string",
4613+
"title": "Stage",
4614+
"default": ""
46214615
},
46224616
"track": {
4623-
"anyOf": [
4624-
{
4625-
"type": "string"
4626-
},
4627-
{
4628-
"type": "null"
4629-
}
4630-
],
4631-
"title": "Track"
4617+
"type": "string",
4618+
"title": "Track",
4619+
"default": ""
46324620
},
46334621
"branch": {
4634-
"anyOf": [
4635-
{
4636-
"type": "string"
4637-
},
4638-
{
4639-
"type": "null"
4640-
}
4641-
],
4642-
"title": "Branch"
4622+
"type": "string",
4623+
"title": "Branch",
4624+
"default": ""
46434625
}
46444626
},
46454627
"type": "object",
@@ -4659,36 +4641,15 @@
46594641
"$ref": "#/components/schemas/FamilyName"
46604642
},
46614643
"stage": {
4662-
"anyOf": [
4663-
{
4664-
"type": "string"
4665-
},
4666-
{
4667-
"type": "null"
4668-
}
4669-
],
4644+
"type": "string",
46704645
"title": "Stage"
46714646
},
46724647
"track": {
4673-
"anyOf": [
4674-
{
4675-
"type": "string"
4676-
},
4677-
{
4678-
"type": "null"
4679-
}
4680-
],
4648+
"type": "string",
46814649
"title": "Track"
46824650
},
46834651
"branch": {
4684-
"anyOf": [
4685-
{
4686-
"type": "string"
4687-
},
4688-
{
4689-
"type": "null"
4690-
}
4691-
],
4652+
"type": "string",
46924653
"title": "Branch"
46934654
}
46944655
},
@@ -4773,37 +4734,19 @@
47734734
"$ref": "#/components/schemas/FamilyName"
47744735
},
47754736
"stage": {
4776-
"anyOf": [
4777-
{
4778-
"type": "string"
4779-
},
4780-
{
4781-
"type": "null"
4782-
}
4783-
],
4784-
"title": "Stage"
4737+
"type": "string",
4738+
"title": "Stage",
4739+
"default": ""
47854740
},
47864741
"track": {
4787-
"anyOf": [
4788-
{
4789-
"type": "string"
4790-
},
4791-
{
4792-
"type": "null"
4793-
}
4794-
],
4795-
"title": "Track"
4742+
"type": "string",
4743+
"title": "Track",
4744+
"default": ""
47964745
},
47974746
"branch": {
4798-
"anyOf": [
4799-
{
4800-
"type": "string"
4801-
},
4802-
{
4803-
"type": "null"
4804-
}
4805-
],
4806-
"title": "Branch"
4747+
"type": "string",
4748+
"title": "Branch",
4749+
"default": ""
48074750
},
48084751
"team_ids": {
48094752
"items": {
@@ -4831,36 +4774,15 @@
48314774
"$ref": "#/components/schemas/FamilyName"
48324775
},
48334776
"stage": {
4834-
"anyOf": [
4835-
{
4836-
"type": "string"
4837-
},
4838-
{
4839-
"type": "null"
4840-
}
4841-
],
4777+
"type": "string",
48424778
"title": "Stage"
48434779
},
48444780
"track": {
4845-
"anyOf": [
4846-
{
4847-
"type": "string"
4848-
},
4849-
{
4850-
"type": "null"
4851-
}
4852-
],
4781+
"type": "string",
48534782
"title": "Track"
48544783
},
48554784
"branch": {
4856-
"anyOf": [
4857-
{
4858-
"type": "string"
4859-
},
4860-
{
4861-
"type": "null"
4862-
}
4863-
],
4785+
"type": "string",
48644786
"title": "Branch"
48654787
},
48664788
"teams": {

backend/test_observer/controllers/artefact_matching_rules/artefact_matching_rules.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ def update_artefact_matching_rule(
204204
# Check if updated rule would conflict with existing rule
205205
existing_rule = db.execute(
206206
select(ArtefactMatchingRule).where(
207-
ArtefactMatchingRule.family == request.family,
208-
ArtefactMatchingRule.stage == request.stage,
209-
ArtefactMatchingRule.track == request.track,
210-
ArtefactMatchingRule.branch == request.branch,
207+
ArtefactMatchingRule.family == (request.family if request.family is not None else rule.family),
208+
ArtefactMatchingRule.stage == (request.stage if request.stage is not None else rule.stage),
209+
ArtefactMatchingRule.track == (request.track if request.track is not None else rule.track),
210+
ArtefactMatchingRule.branch == (request.branch if request.branch is not None else rule.branch),
211211
ArtefactMatchingRule.id != rule_id,
212212
)
213213
).scalar_one_or_none()

backend/test_observer/controllers/artefact_matching_rules/models.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@
2525
class ArtefactMatchingRuleBase(BaseModel):
2626
"""Base model for artefact matching rule with common fields"""
2727
family: FamilyName
28-
stage: str | None = None
29-
track: str | None = None
30-
branch: str | None = None
28+
stage: str = ""
29+
track: str = ""
30+
branch: str = ""
3131

3232

3333
class ArtefactMatchingRuleInResponse(BaseModel):
3434
"""Artefact matching rule fields when included in responses (no relationships)"""
3535
id: int
3636
family: FamilyName
37-
stage: str | None
38-
track: str | None
39-
branch: str | None
37+
stage: str
38+
track: str
39+
branch: str
4040

4141

4242
class TeamMinimal(BaseModel):
@@ -48,9 +48,9 @@ class ArtefactMatchingRuleResponse(BaseModel):
4848
"""Artefact matching rule with associated teams"""
4949
id: int
5050
family: FamilyName
51-
stage: str | None
52-
track: str | None
53-
branch: str | None
51+
stage: str
52+
track: str
53+
branch: str
5454
teams: list[TeamMinimal]
5555

5656

backend/test_observer/controllers/test_executions/start_test.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@
1717
from datetime import date, timedelta
1818

1919
from fastapi import APIRouter, Body, Depends, Security
20-
from sqlalchemy import select, and_, or_
20+
from sqlalchemy import and_, or_, select
2121
from sqlalchemy.orm import Session
2222

2323
from test_observer.common.permissions import Permission, permission_checker
2424
from test_observer.data_access.models import (
2525
Artefact,
2626
ArtefactBuild,
2727
ArtefactBuildEnvironmentReview,
28+
ArtefactMatchingRule,
2829
Environment,
2930
Team,
3031
TestExecution,
3132
TestPlan,
3233
User,
33-
ArtefactMatchingRule,
3434
)
3535
from test_observer.data_access.repository import (
3636
create_test_execution_relevant_link,
@@ -88,14 +88,17 @@ def assign_reviewer(self):
8888
.where(
8989
and_(
9090
ArtefactMatchingRule.family == family_str,
91-
or_(ArtefactMatchingRule.stage == self.artefact.stage, ArtefactMatchingRule.stage.is_(None)),
92-
or_(ArtefactMatchingRule.track == self.artefact.track, ArtefactMatchingRule.track.is_(None)),
93-
or_(ArtefactMatchingRule.branch == self.artefact.branch, ArtefactMatchingRule.branch.is_(None)),
91+
or_(ArtefactMatchingRule.stage == self.artefact.stage, ArtefactMatchingRule.stage == ""),
92+
or_(ArtefactMatchingRule.track == self.artefact.track, ArtefactMatchingRule.track == ""),
93+
or_(ArtefactMatchingRule.branch == self.artefact.branch, ArtefactMatchingRule.branch == ""),
9494
),
9595
)).scalars().all()
9696

97-
# sort rules by number of non-null fields to prioritize specificity
98-
rules_with_score = [[r, sum(1 for field in [r.stage, r.track, r.branch] if field is not None)] for r in possible_rules]
97+
# sort rules by number of non-empty fields to prioritize specificity
98+
rules_with_score = [
99+
[r, sum(1 for field in [r.stage, r.track, r.branch] if field != "")]
100+
for r in possible_rules
101+
]
99102
sorted_rules = sorted(rules_with_score, key=lambda x: x[1], reverse=True)
100103
highest_score = sorted_rules[0][1] if sorted_rules else 0
101104
rules = [r[0] for r in sorted_rules if r[1] == highest_score]

backend/test_observer/data_access/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,9 @@ class ArtefactMatchingRule(Base):
196196
__tablename__ = "artefact_matching_rule"
197197

198198
family: Mapped[FamilyName]
199-
stage: Mapped[str | None] = mapped_column(String(100), default=None)
200-
track: Mapped[str | None] = mapped_column(String(200), default=None)
201-
branch: Mapped[str | None] = mapped_column(String(200), default=None)
199+
stage: Mapped[str] = mapped_column(String(100), default="", server_default="")
200+
track: Mapped[str] = mapped_column(String(200), default="", server_default="")
201+
branch: Mapped[str] = mapped_column(String(200), default="", server_default="")
202202

203203
teams: Mapped[list[Team]] = relationship(
204204
secondary="artefact_matching_rule_team_association",
@@ -207,7 +207,7 @@ class ArtefactMatchingRule(Base):
207207

208208
__table_args__ = (
209209
UniqueConstraint(
210-
"family", "stage", "track", "branch", postgresql_nulls_not_distinct=True
210+
"family", "stage", "track", "branch"
211211
),
212212
)
213213

backend/tests/controllers/artefact_matching_rules/test_artefact_matching_rules.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def test_create_artefact_matching_rule(test_client: TestClient, generator: DataG
4141
data = response.json()
4242
assert data["family"] == "snap"
4343
assert data["track"] == "22"
44-
assert data["stage"] is None
45-
assert data["branch"] is None
44+
assert data["stage"] == ""
45+
assert data["branch"] == ""
4646
assert len(data["teams"]) == 1
4747
assert data["teams"][0]["id"] == team.id
4848
assert "id" in data
@@ -90,9 +90,9 @@ def test_create_artefact_matching_rule_family_only(test_client: TestClient, gene
9090
assert response.status_code == 201
9191
data = response.json()
9292
assert data["family"] == "deb"
93-
assert data["track"] is None
94-
assert data["stage"] is None
95-
assert data["branch"] is None
93+
assert data["track"] == ""
94+
assert data["stage"] == ""
95+
assert data["branch"] == ""
9696
assert len(data["teams"]) == 1
9797

9898

@@ -305,7 +305,7 @@ def test_update_artefact_matching_rule(
305305
assert data["family"] == "snap"
306306
assert data["track"] == "24"
307307
assert data["stage"] == "stable"
308-
assert data["branch"] is None
308+
assert data["branch"] == ""
309309
# Teams should be preserved
310310
assert len(data["teams"]) == 1
311311
assert data["teams"][0]["id"] == team.id

0 commit comments

Comments
 (0)