Skip to content

Commit 581bc26

Browse files
test: package from newest evaluation shows up in suggestion
1 parent c1d085e commit 581bc26

2 files changed

Lines changed: 120 additions & 37 deletions

File tree

src/shared/tests/fixtures.py

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import secrets
2+
from collections.abc import Callable
3+
14
import pytest
25
from allauth.socialaccount.models import SocialAccount
36
from django.contrib.auth.models import AbstractBaseUser
@@ -19,6 +22,7 @@
1922
ProvenanceFlags,
2023
)
2124
from shared.models.nix_evaluation import (
25+
MAJOR_CHANNELS,
2226
NixChannel,
2327
NixDerivation,
2428
NixDerivationMeta,
@@ -49,51 +53,75 @@ def cve(db: None) -> Container:
4953

5054

5155
@pytest.fixture
52-
def evaluation(db: None) -> NixEvaluation:
53-
channel = NixChannel.objects.create(
54-
staging_branch="release-24.05",
55-
channel_branch="release-24.05",
56+
def channel(db: None) -> NixChannel:
57+
# FIXME(@fricklerhandwerk): This will fall apart when we obtain the channel structure dynamically [ref:channel-structure]
58+
release = MAJOR_CHANNELS[1]
59+
return NixChannel.objects.create(
60+
staging_branch=f"nixos-{release}",
61+
channel_branch=f"nixos-{release}",
5662
head_sha1_commit="deadbeef",
5763
state=NixChannel.ChannelState.STABLE,
58-
release_version="24.05",
64+
release_version=release,
5965
repository="https://github.com/NixOS/nixpkgs",
6066
)
6167

62-
evaluation = NixEvaluation.objects.create(
63-
channel=channel,
64-
commit_sha1="deadbeef",
65-
state="completed",
66-
)
67-
return evaluation
68+
69+
@pytest.fixture
70+
def make_evaluation(channel: NixChannel) -> Callable[[], NixEvaluation]:
71+
def wrapped() -> NixEvaluation:
72+
return NixEvaluation.objects.create(
73+
channel=channel,
74+
commit_sha1=secrets.token_hex(16),
75+
state="completed",
76+
)
77+
78+
return wrapped
79+
80+
81+
@pytest.fixture
82+
def evaluation(make_evaluation: Callable[[], NixEvaluation]) -> NixEvaluation:
83+
return make_evaluation()
6884

6985

7086
@pytest.fixture
71-
def drv(db: None, evaluation: NixEvaluation) -> NixDerivation:
72-
maintainer = NixMaintainer.objects.create(
87+
def maintainer(db: None) -> NixMaintainer:
88+
return NixMaintainer.objects.create(
7389
github_id=123, github="testuser", name="Test User", email="test@example.com"
7490
)
7591

76-
meta = NixDerivationMeta.objects.create(
77-
description="First dummy derivation",
78-
insecure=False,
79-
available=True,
80-
broken=False,
81-
unfree=False,
82-
unsupported=False,
83-
)
8492

85-
meta.maintainers.add(maintainer)
93+
@pytest.fixture
94+
def make_drv(maintainer: NixMaintainer) -> Callable[[NixEvaluation], NixDerivation]:
95+
def wrapped(evaluation: NixEvaluation) -> NixDerivation:
96+
meta = NixDerivationMeta.objects.create(
97+
description="Dummy derivation",
98+
insecure=False,
99+
available=True,
100+
broken=False,
101+
unfree=False,
102+
unsupported=False,
103+
)
104+
meta.maintainers.add(maintainer)
105+
106+
return NixDerivation.objects.create(
107+
attribute="foo",
108+
derivation_path="/nix/store/<hash>-foo.drv",
109+
name="foo-1.0",
110+
metadata=meta,
111+
system="x86_64-linux",
112+
parent_evaluation=evaluation,
113+
)
114+
115+
return wrapped
86116

87-
drv = NixDerivation.objects.create(
88-
attribute="foo",
89-
derivation_path="/nix/store/<hash>-foo.drv",
90-
name="foo-1.0",
91-
metadata=meta,
92-
system="x86_64-linux",
93-
parent_evaluation=evaluation,
94-
)
95117

96-
return drv
118+
@pytest.fixture
119+
def drv(
120+
db: None,
121+
make_drv: Callable[[NixEvaluation], NixDerivation],
122+
evaluation: NixEvaluation,
123+
) -> NixDerivation:
124+
return make_drv(evaluation)
97125

98126

99127
@pytest.fixture
Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,75 @@
1+
from collections.abc import Callable
2+
from datetime import timedelta
3+
14
from shared.listeners.cache_suggestions import cache_new_suggestions
25
from shared.models.cached import CachedSuggestions
36
from shared.models.cve import Container
4-
from shared.models.linkage import CVEDerivationClusterProposal
5-
from shared.models.nix_evaluation import NixDerivation
7+
from shared.models.linkage import (
8+
CVEDerivationClusterProposal,
9+
DerivationClusterProposalLink,
10+
ProvenanceFlags,
11+
)
12+
from shared.models.nix_evaluation import (
13+
NixDerivation,
14+
NixEvaluation,
15+
)
616

717

8-
# XXX(@fricklerhandwerk): This test isn't doing a lot except run the code once and assert that the result is non-empty.
918
# The Pydantic class for the cached value gives us some assurance about the shape of the data, but ultimately we probabyly want property tests here.
10-
def test_caching(
19+
def test_caching_newest_package(
1120
cve: Container,
12-
drv: NixDerivation,
21+
make_evaluation: Callable[[], NixEvaluation],
22+
make_drv: Callable[[NixEvaluation], NixDerivation],
1323
suggestion: CVEDerivationClusterProposal,
1424
) -> None:
25+
"""
26+
Check that when aggregating derivations from a suggestion, only the newest one for a given package name (== attribute path) is used.
27+
"""
28+
29+
# Order of creation matters for triggering the bug.
30+
# This is brittle because it assumes things about the database, but it seems that derivations are scanned in insertion order of their evaluations.
31+
eval_new = make_evaluation()
32+
eval_old = make_evaluation()
33+
# Overwrite the timestamp without triggering the `save()` hook that would write the current time again
34+
target_time = eval_old.created_at - timedelta(hours=1)
35+
NixEvaluation.objects.filter(pk=eval_old.pk).update(
36+
created_at=target_time,
37+
updated_at=target_time,
38+
)
39+
drv2 = make_drv(eval_new)
40+
drv1 = make_drv(eval_old)
41+
42+
# Use different versions. We'll assert over those since that's what's primarily visible to users
43+
drv1.name = f"{drv1.attribute}-1.0"
44+
drv1.save()
45+
drv2.name = f"{drv1.attribute}-2.0"
46+
drv2.save()
47+
48+
suggestion = CVEDerivationClusterProposal.objects.create(
49+
status="pending",
50+
cve=cve.cve,
51+
)
52+
53+
DerivationClusterProposalLink.objects.create(
54+
proposal=suggestion,
55+
derivation=drv1,
56+
provenance_flags=ProvenanceFlags.PACKAGE_NAME_MATCH,
57+
)
58+
59+
DerivationClusterProposalLink.objects.create(
60+
proposal=suggestion,
61+
derivation=drv2,
62+
provenance_flags=ProvenanceFlags.PACKAGE_NAME_MATCH,
63+
)
64+
1565
cached = CachedSuggestions.objects.filter(proposal=suggestion)
1666
assert not cached.exists()
1767
cache_new_suggestions(suggestion)
1868
value = cached.get()
1969
assert cve.cve.cve_id == value.payload["cve_id"]
20-
assert drv.attribute in value.payload["packages"]
70+
71+
channel, package = next(
72+
iter(value.payload["packages"][drv1.attribute]["channels"].items())
73+
)
74+
75+
assert package["major_version"] == "2.0"

0 commit comments

Comments
 (0)