Merge ~andrey-fedoseev/launchpad:bug-presense into launchpad:master

Proposed by Andrey Fedoseev
Status: Needs review
Proposed branch: ~andrey-fedoseev/launchpad:bug-presense
Merge into: launchpad:master
Diff against target: 769 lines (+691/-0)
7 files modified
lib/lp/bugs/configure.zcml (+25/-0)
lib/lp/bugs/interfaces/bugpresence.py (+103/-0)
lib/lp/bugs/model/bugpresence.py (+150/-0)
lib/lp/bugs/security.py (+33/-0)
lib/lp/bugs/tests/test_bugpresence.py (+337/-0)
lib/lp/code/model/gitrepository.py (+10/-0)
lib/lp/testing/factory.py (+33/-0)
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+431710@code.launchpad.net

Commit message

Add `BugPresence` model

Description of the change

It represents a range of versions or git commits in which the bug was present.

To post a comment you must log in.

Unmerged commits

460e925... by Andrey Fedoseev

Add `BugPresence` model

It represents a range of versions or git commits in which the bug was present.

Succeeded
[SUCCEEDED] docs:0 (build)
[SUCCEEDED] lint:0 (build)
[SUCCEEDED] mypy:0 (build)
13 of 3 results

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
index 6eaa58c..52977cd 100644
--- a/lib/lp/bugs/configure.zcml
+++ b/lib/lp/bugs/configure.zcml
@@ -1113,4 +1113,29 @@
1113 >1113 >
1114 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>1114 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
1115 </securedutility>1115 </securedutility>
1116
1117 <!-- BugPresence -->
1118 <class
1119 class="lp.bugs.model.bugpresence.BugPresence">
1120 <require
1121 permission="launchpad.View"
1122 interface="lp.bugs.interfaces.bugpresence.IBugPresenceView"/>
1123 <require
1124 permission="launchpad.Edit"
1125 interface="lp.bugs.interfaces.bugpresence.IBugPresenceEdit" />
1126 </class>
1127
1128 <!-- BugPresenceSet -->
1129 <class
1130 class="lp.bugs.model.bugpresence.BugPresenceSet">
1131 <allow
1132 interface="lp.bugs.interfaces.bugpresence.IBugPresenceSet"/>
1133 </class>
1134 <securedutility
1135 class="lp.bugs.model.bugpresence.BugPresenceSet"
1136 provides="lp.bugs.interfaces.bugpresence.IBugPresenceSet">
1137 <allow
1138 interface="lp.bugs.interfaces.bugpresence.IBugPresenceSet"/>
1139 </securedutility>
1140
1116</configure>1141</configure>
diff --git a/lib/lp/bugs/interfaces/bugpresence.py b/lib/lp/bugs/interfaces/bugpresence.py
1117new file mode 1006441142new file mode 100644
index 0000000..4a34970
--- /dev/null
+++ b/lib/lp/bugs/interfaces/bugpresence.py
@@ -0,0 +1,103 @@
1from lazr.restful.fields import Reference
2from zope.interface import Attribute, Interface
3from zope.schema import Choice, Int, TextLine
4
5from lp import _
6from lp.services.fields import BugField
7
8__all__ = ["IBugPresence", "IBugPresenceSet"]
9
10
11class IBugPresenceView(Interface):
12 """IBugPresence attributes that require launchpad.View permission."""
13
14 id = Int()
15 bug = BugField(title=_("Bug"), readonly=True)
16 bug_id = Int()
17
18 project = Choice(title=_("Project"), required=False, vocabulary="Product")
19 project_id = Attribute("The product ID")
20 source_package_name = Choice(
21 title=_("Package"), required=False, vocabulary="SourcePackageName"
22 )
23 source_package_name_id = Attribute("The source_package_name ID")
24 distribution = Choice(
25 title=_("Distribution"), required=False, vocabulary="Distribution"
26 )
27 distribution_id = Attribute("The distribution ID")
28
29 broken_version = TextLine(
30 required=False, title=_("Version that introduced the bug")
31 )
32 fixed_version = TextLine(
33 required=False, title=_("Version that fixed the bug")
34 )
35
36 git_repository = Choice(
37 title=_("Git repository"), required=False, vocabulary="GitRepository"
38 )
39 git_repository_id = Attribute("The git repository ID")
40 broken_git_commit_sha1 = TextLine(
41 required=False,
42 title=_("Git commit that introduced the bug"),
43 max_length=40,
44 )
45 fixed_git_commit_sha1 = TextLine(
46 required=False, title=_("Git commit that fixed the bug"), max_length=40
47 )
48
49 target = Reference(
50 title=_("Target"),
51 required=True,
52 schema=Interface, # IBugTarget|IGitRepository
53 )
54
55
56class IBugPresenceEdit(Interface):
57 """IBugPresence attributes that require launchpad.Edit permission."""
58
59 def destroySelf():
60 """Delete the specified bug presence."""
61
62
63class IBugPresence(IBugPresenceView, IBugPresenceEdit):
64 """
65 Represents a range of versions or git commits in which the bug was present.
66 """
67
68
69class IBugPresenceSet(Interface):
70 def new(
71 bug,
72 target,
73 broken_version,
74 fixed_version,
75 broken_git_commit_sha1,
76 fixed_git_commit_sha1,
77 ):
78 """Create new BugPresence instance.
79 :param bug: a bug to create a bug presence for
80 :param target: a project, a distribution, a distribution package or
81 a git repository
82 :param broken_version: version in which the bug was introduced
83 :param fixed_version: version in which the bug was fixed
84 :param broken_git_commit_sha1: git commit in which the bug
85 was introduced (for git repository)
86 :param fixed_git_commit_sha1: git commit in which the bug
87 was fixed (for git repository)
88 """
89 pass
90
91 def getByBug(bug):
92 """Get all BugPresence instances for the given bug.
93 :param bug: a bug to get the bug presence instances from
94 :return: a collection of BugPresence instances
95 """
96 pass
97
98 def getByTarget(target):
99 """Get all BugPresence instances for the given target.
100 :param target: a target to get the bug presence instances for
101 :return: a collection of BugPresence instances
102 """
103 pass
diff --git a/lib/lp/bugs/model/bugpresence.py b/lib/lp/bugs/model/bugpresence.py
0new file mode 100644104new file mode 100644
index 0000000..b21371e
--- /dev/null
+++ b/lib/lp/bugs/model/bugpresence.py
@@ -0,0 +1,150 @@
1from storm.properties import Int, Unicode
2from storm.references import Reference
3from storm.store import Store
4from zope.interface import implementer
5
6from lp.bugs.interfaces.bugpresence import IBugPresence, IBugPresenceSet
7from lp.code.interfaces.gitrepository import IGitRepository
8from lp.registry.interfaces.distributionsourcepackage import (
9 IDistributionSourcePackage,
10)
11from lp.registry.interfaces.product import IProduct
12from lp.services.database.interfaces import IStore
13from lp.services.database.stormbase import StormBase
14
15__all__ = ["BugPresence", "BugPresenceSet"]
16
17
18@implementer(IBugPresence)
19class BugPresence(StormBase):
20 """See `IBugPresence`."""
21
22 __storm_table__ = "BugPresence"
23
24 id = Int(primary=True)
25
26 bug_id = Int(name="bug", allow_none=False)
27 bug = Reference(bug_id, "Bug.id")
28
29 project_id = Int(name="project", allow_none=True)
30 project = Reference(project_id, "Product.id")
31
32 source_package_name_id = Int(name="source_package_name", allow_none=True)
33 source_package_name = Reference(
34 source_package_name_id, "SourcePackageName.id"
35 )
36
37 distribution_id = Int(name="distribution", allow_none=True)
38 distribution = Reference(distribution_id, "Distribution.id")
39
40 broken_version = Unicode(name="broken_version", allow_none=True)
41 fixed_version = Unicode(name="fixed_version", allow_none=True)
42
43 git_repository_id = Int(name="git_repository", allow_none=True)
44 git_repository = Reference(git_repository_id, "GitRepository.id")
45
46 broken_git_commit_sha1 = Unicode(
47 name="broken_git_commit_sha1", allow_none=True
48 )
49 fixed_git_commit_sha1 = Unicode(
50 name="fixed_git_commit_sha1", allow_none=True
51 )
52
53 def __init__(
54 self,
55 bug,
56 target,
57 broken_version=None,
58 fixed_version=None,
59 broken_git_commit_sha1=None,
60 fixed_git_commit_sha1=None,
61 ):
62 self.bug = bug
63 self.target = target
64 self.broken_version = broken_version
65 self.fixed_version = fixed_version
66 self.broken_git_commit_sha1 = broken_git_commit_sha1
67 self.fixed_git_commit_sha1 = fixed_git_commit_sha1
68
69 @property
70 def target(self):
71 if self.project:
72 return self.project
73 elif self.source_package_name:
74 return self.distribution.getSourcePackage(self.source_package_name)
75 elif self.git_repository:
76 return self.git_repository
77 else:
78 raise AssertionError("Could not determine BugPresence target")
79
80 @target.setter
81 def target(self, target):
82 if IProduct.providedBy(target):
83 self.project = target
84 elif IDistributionSourcePackage.providedBy(target):
85 self.distribution = target.distribution
86 self.source_package_name = target.sourcepackagename
87 elif IGitRepository.providedBy(target):
88 self.git_repository = target
89 else:
90 raise AssertionError("Invalid BugPresence target.")
91
92 def destroySelf(self):
93 Store.of(self).remove(self)
94
95
96@implementer(IBugPresenceSet)
97class BugPresenceSet:
98 """See `IBugPresenceSet`."""
99
100 def new(
101 self,
102 bug,
103 target,
104 broken_version=None,
105 fixed_version=None,
106 broken_git_commit_sha1=None,
107 fixed_git_commit_sha1=None,
108 ):
109
110 is_version = bool(broken_version or fixed_version)
111 is_git_range = bool(broken_git_commit_sha1 or fixed_git_commit_sha1)
112
113 if is_version == is_git_range:
114 raise ValueError(
115 "Either broken_version/fixed_version or "
116 "broken_git_commit_sha1/fixed_git_commit_sha1 "
117 "must be specified"
118 )
119
120 if target is None:
121 raise ValueError("target must be specified")
122
123 if is_git_range and not IGitRepository.providedBy(target):
124 raise ValueError("target must be a git repository")
125
126 return BugPresence(
127 bug=bug,
128 target=target,
129 broken_version=broken_version,
130 fixed_version=fixed_version,
131 broken_git_commit_sha1=broken_git_commit_sha1,
132 fixed_git_commit_sha1=fixed_git_commit_sha1,
133 )
134
135 def getByBug(self, bug):
136 return IStore(BugPresence).find(BugPresence, bug=bug)
137
138 def getByTarget(self, target):
139 conditions = {}
140 if IProduct.providedBy(target):
141 conditions["project"] = target
142 elif IDistributionSourcePackage.providedBy(target):
143 conditions["distribution"] = target.distribution
144 conditions["source_package_name"] = target.sourcepackagename
145 elif IGitRepository.providedBy(target):
146 conditions["git_repository"] = target
147 else:
148 raise AssertionError("Invalid BugPresence target.")
149
150 return IStore(BugPresence).find(BugPresence, **conditions)
diff --git a/lib/lp/bugs/security.py b/lib/lp/bugs/security.py
index 11d74e9..15ae315 100644
--- a/lib/lp/bugs/security.py
+++ b/lib/lp/bugs/security.py
@@ -17,6 +17,7 @@ from lp.bugs.interfaces.bug import IBug
17from lp.bugs.interfaces.bugactivity import IBugActivity17from lp.bugs.interfaces.bugactivity import IBugActivity
18from lp.bugs.interfaces.bugattachment import IBugAttachment18from lp.bugs.interfaces.bugattachment import IBugAttachment
19from lp.bugs.interfaces.bugnomination import IBugNomination19from lp.bugs.interfaces.bugnomination import IBugNomination
20from lp.bugs.interfaces.bugpresence import IBugPresence
20from lp.bugs.interfaces.bugsubscription import IBugSubscription21from lp.bugs.interfaces.bugsubscription import IBugSubscription
21from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter22from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
22from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor23from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
@@ -455,3 +456,35 @@ class BugTargetOwnerOrBugSupervisorOrAdmins(AuthorizationBase):
455 or user.inTeam(self.obj.owner)456 or user.inTeam(self.obj.owner)
456 or user.in_admin457 or user.in_admin
457 )458 )
459
460
461class ViewBugPresence(DelegatedAuthorization):
462 """
463 A person that can view a Bug can also view a related BugPresence.
464 """
465
466 permission = "launchpad.View"
467 usedfor = IBugPresence
468
469 def __init__(self, obj):
470 super().__init__(obj, obj.bug, "launchpad.View")
471
472 def checkAuthenticated(self, user):
473 r = super().checkAuthenticated(user)
474 return r
475
476 def checkUnauthenticated(self):
477 r = super().checkUnauthenticated()
478 return r
479
480
481class EditBugPresence(DelegatedAuthorization):
482 """
483 A person that can edit a Bug can also edit a related BugPresence.
484 """
485
486 permission = "launchpad.Edit"
487 usedfor = IBugPresence
488
489 def __init__(self, obj):
490 super().__init__(obj, obj.bug, "launchpad.Edit")
diff --git a/lib/lp/bugs/tests/test_bugpresence.py b/lib/lp/bugs/tests/test_bugpresence.py
458new file mode 100644491new file mode 100644
index 0000000..e328dff
--- /dev/null
+++ b/lib/lp/bugs/tests/test_bugpresence.py
@@ -0,0 +1,337 @@
1import transaction
2from psycopg2.errors import CheckViolation
3from zope.component import getUtility
4from zope.security import checkPermission
5
6from lp.app.enums import InformationType
7from lp.bugs.interfaces.bugpresence import IBugPresenceSet
8from lp.bugs.model.bugpresence import BugPresence
9from lp.registry.model.distributionsourcepackage import (
10 DistributionSourcePackage,
11)
12from lp.services.database.interfaces import IStore
13from lp.testing import (
14 TestCaseWithFactory,
15 anonymous_logged_in,
16 person_logged_in,
17)
18from lp.testing.layers import DatabaseFunctionalLayer, ZopelessDatabaseLayer
19
20
21class TestBugPresence(TestCaseWithFactory):
22
23 layer = ZopelessDatabaseLayer
24
25 def test_target(self):
26 project = self.factory.makeProduct()
27 distribution = self.factory.makeDistribution()
28 spn = self.factory.makeSourcePackageName()
29
30 for target, attrs in (
31 (project, {"project": project}),
32 (
33 DistributionSourcePackage(distribution, spn),
34 {"distribution": distribution, "source_package_name": spn},
35 ),
36 ):
37 bug_presence = BugPresence(
38 bug=self.factory.makeBug(),
39 target=target,
40 broken_version="1",
41 )
42 self.assertEqual(target, bug_presence.target)
43 for attr_name, value in attrs.items():
44 self.assertEqual(value, getattr(bug_presence, attr_name))
45
46 git_repository = self.factory.makeGitRepository()
47 bug_presence = BugPresence(
48 bug=self.factory.makeBug(),
49 target=git_repository,
50 broken_git_commit_sha1="1" * 40,
51 )
52 self.assertEqual(git_repository, bug_presence.target)
53 self.assertEqual(git_repository, bug_presence.git_repository)
54
55 def test_constraints(self):
56 project = self.factory.makeProduct()
57 distro_package = self.factory.makeDistributionSourcePackage()
58 git_repository = self.factory.makeGitRepository()
59
60 store = IStore(BugPresence)
61 store.commit()
62
63 # no version range, nor git commit range
64 for target in project, distro_package, git_repository:
65 BugPresence(bug=self.factory.makeBug(), target=target)
66 self.assertRaises(CheckViolation, store.flush)
67 store.rollback()
68
69 # invalid version target
70 BugPresence(
71 bug=self.factory.makeBug(),
72 target=git_repository,
73 broken_version="1",
74 )
75 self.assertRaises(CheckViolation, store.flush)
76 store.rollback()
77
78 # invalid git range target
79 for target in project, distro_package:
80 BugPresence(
81 bug=self.factory.makeBug(),
82 target=target,
83 broken_git_commit_sha1="1" * 40,
84 )
85 self.assertRaises(CheckViolation, store.flush)
86 store.rollback()
87
88 # valid version range
89 for broken_version, fixed_version in (
90 ("1", None),
91 (None, "2"),
92 ("1", "2"),
93 ):
94 for target in project, distro_package:
95 bp = BugPresence(
96 bug=self.factory.makeBug(),
97 target=target,
98 broken_version=broken_version,
99 fixed_version=fixed_version,
100 )
101 store.commit()
102 bp = store.get(BugPresence, bp.id)
103 self.assertEqual(broken_version, bp.broken_version)
104 self.assertEqual(fixed_version, bp.fixed_version)
105
106 # valid git commit range
107 for broken_git_commit_sha1, fixed_git_commit_sha1 in (
108 ("1" * 40, None),
109 (None, "2" * 40),
110 ("1" * 40, "2" * 40),
111 ):
112 bp = BugPresence(
113 bug=self.factory.makeBug(),
114 target=git_repository,
115 broken_git_commit_sha1=broken_git_commit_sha1,
116 fixed_git_commit_sha1=fixed_git_commit_sha1,
117 )
118 store.commit()
119 bp = store.get(BugPresence, bp.id)
120 self.assertEqual(broken_git_commit_sha1, bp.broken_git_commit_sha1)
121 self.assertEqual(fixed_git_commit_sha1, bp.fixed_git_commit_sha1)
122
123 def test_referenced_git_repository_can_be_deleted(self):
124 git_repository = self.factory.makeGitRepository()
125 bug = self.factory.makeBug()
126 bug_presence_to_delete = self.factory.makeBugPresence(
127 bug=bug,
128 target=git_repository,
129 broken_git_commit_sha1="1" * 40,
130 )
131 bug_presence_to_keep = self.factory.makeBugPresence(bug=bug)
132 git_repository.destroySelf(break_references=True)
133 transaction.commit()
134 bug_presences = list(getUtility(IBugPresenceSet).getByBug(bug))
135 self.assertIn(bug_presence_to_keep, bug_presences)
136 self.assertNotIn(bug_presence_to_delete, bug_presences)
137
138
139class TestBugPresenceSecurity(TestCaseWithFactory):
140
141 layer = DatabaseFunctionalLayer
142
143 def test_view_permission(self):
144 project = self.factory.makeProduct()
145
146 # Anyone can view a public bug presence
147 public_bug = self.factory.makeBug(
148 target=project, information_type=InformationType.PUBLIC
149 )
150 bp = BugPresence(bug=public_bug, target=project, broken_version="1")
151 with anonymous_logged_in():
152 self.assertTrue(checkPermission("launchpad.View", bp))
153
154 # Anonymous and random users can't see a private bug presence
155 owner = self.factory.makePerson()
156 project = self.factory.makeProduct(
157 information_type=InformationType.PROPRIETARY, owner=owner
158 )
159 with person_logged_in(owner):
160 private_bug = self.factory.makeBug(
161 target=project,
162 information_type=InformationType.PROPRIETARY,
163 owner=owner,
164 )
165 bp = BugPresence(bug=private_bug, target=project, broken_version="1")
166 with anonymous_logged_in():
167 self.assertFalse(checkPermission("launchpad.View", bp))
168 with person_logged_in(self.factory.makePerson()):
169 self.assertFalse(checkPermission("launchpad.View", bp))
170
171 # Private bug owners can see a private bug presence
172 with person_logged_in(owner):
173 self.assertTrue(checkPermission("launchpad.View", bp))
174
175 def test_edit_permission(self):
176 project = self.factory.makeProduct()
177
178 public_bug = self.factory.makeBug(
179 target=project, information_type=InformationType.PUBLIC
180 )
181 bp = BugPresence(bug=public_bug, target=project, broken_version="1")
182 # Anonymous can't edit a public bug presence
183 with anonymous_logged_in():
184 self.assertFalse(checkPermission("launchpad.Edit", bp))
185
186 # Any authenticated user can edit a public bug presence
187 with person_logged_in(self.factory.makePerson()):
188 self.assertTrue(checkPermission("launchpad.Edit", bp))
189
190 # Anonymous and random users can't edit a private bug presence
191 owner = self.factory.makePerson()
192 project = self.factory.makeProduct(
193 information_type=InformationType.PROPRIETARY, owner=owner
194 )
195 with person_logged_in(owner):
196 private_bug = self.factory.makeBug(
197 target=project,
198 information_type=InformationType.PROPRIETARY,
199 owner=owner,
200 )
201 bp = BugPresence(bug=private_bug, target=project, broken_version="1")
202 with anonymous_logged_in():
203 self.assertFalse(checkPermission("launchpad.Edit", bp))
204 with person_logged_in(self.factory.makePerson()):
205 self.assertFalse(checkPermission("launchpad.Edit", bp))
206
207 # Private bug owners can edit a private bug presence
208 with person_logged_in(owner):
209 self.assertTrue(checkPermission("launchpad.Edit", bp))
210
211
212class TestBugPresenceSet(TestCaseWithFactory):
213
214 layer = DatabaseFunctionalLayer
215
216 def test_new__version_range(self):
217 project = self.factory.makeProduct()
218 distro_package = self.factory.makeDistributionSourcePackage()
219
220 bug_presence_set = getUtility(IBugPresenceSet)
221
222 # version range
223 for broken_version, fixed_version in (
224 ("1", None),
225 (None, "2"),
226 ("1", "2"),
227 ):
228 for target in project, distro_package:
229 bug = self.factory.makeBug()
230 bp = bug_presence_set.new(
231 bug=bug,
232 target=target,
233 broken_version=broken_version,
234 fixed_version=fixed_version,
235 )
236 self.assertEqual(bug, bp.bug)
237 self.assertEqual(target, bp.target)
238 self.assertEqual(broken_version, bp.broken_version)
239 self.assertEqual(fixed_version, bp.fixed_version)
240
241 def test_new__git_commit_range(self):
242 git_repository = self.factory.makeGitRepository()
243
244 bug_presence_set = getUtility(IBugPresenceSet)
245
246 # version range
247 for broken_git_commit_sha1, fixed_git_commit_sha1 in (
248 ("1" * 40, None),
249 (None, "2" * 40),
250 ("1" * 40, "2" * 40),
251 ):
252 bug = self.factory.makeBug()
253 bp = bug_presence_set.new(
254 bug=bug,
255 target=git_repository,
256 broken_git_commit_sha1=broken_git_commit_sha1,
257 fixed_git_commit_sha1=fixed_git_commit_sha1,
258 )
259 self.assertEqual(bug, bp.bug)
260 self.assertEqual(git_repository, bp.target)
261 self.assertEqual(broken_git_commit_sha1, bp.broken_git_commit_sha1)
262 self.assertEqual(fixed_git_commit_sha1, bp.fixed_git_commit_sha1)
263
264 def test_new__invalid_arguments(self):
265 project = self.factory.makeProduct()
266 bug = self.factory.makeBug()
267 bug_presence_set = getUtility(IBugPresenceSet)
268
269 # no version range nor git commit range is specified
270 self.assertRaises(
271 ValueError, bug_presence_set.new, bug=bug, target=project
272 )
273
274 # both version range and commit range is specified
275 self.assertRaises(
276 ValueError,
277 bug_presence_set.new,
278 bug=bug,
279 target=project,
280 broken_version="1",
281 fixed_git_commit_sha1="2",
282 )
283
284 # target is not a git repository for git commit range
285 self.assertRaises(
286 ValueError,
287 bug_presence_set.new,
288 bug=bug,
289 target=project,
290 fixed_git_commit_sha1="2",
291 )
292
293 # target is missing
294 self.assertRaises(
295 ValueError,
296 bug_presence_set.new,
297 bug=bug,
298 target=None,
299 broken_version="1",
300 )
301
302 def test_getByBug(self):
303 project = self.factory.makeProduct()
304 bug = self.factory.makeBug()
305 another_bug = self.factory.makeBug()
306 bug_presence_set = getUtility(IBugPresenceSet)
307
308 self.assertEqual([], list(bug_presence_set.getByBug(bug)))
309 self.assertEqual([], list(bug_presence_set.getByBug(another_bug)))
310
311 bp_1 = bug_presence_set.new(
312 bug=bug, target=project, broken_version="1"
313 )
314 bp_2 = bug_presence_set.new(bug=bug, target=project, fixed_version="2")
315 bp_3 = bug_presence_set.new(
316 bug=another_bug, target=project, broken_version="3"
317 )
318 self.assertEqual({bp_1, bp_2}, set(bug_presence_set.getByBug(bug)))
319 self.assertEqual({bp_3}, set(bug_presence_set.getByBug(another_bug)))
320
321
322class TestBugPresenceFactory(TestCaseWithFactory):
323
324 layer = ZopelessDatabaseLayer
325
326 def test_makeBugPresence__no_arguments(self):
327 bp = self.factory.makeBugPresence()
328 self.assertIsNotNone(bp.target)
329 self.assertIsNotNone(bp.broken_version)
330 self.assertIsNotNone(bp.fixed_version)
331
332 def test_makeBugPresence__git_repository(self):
333 git_repository = self.factory.makeGitRepository()
334 bp = self.factory.makeBugPresence(target=git_repository)
335 self.assertEqual(git_repository, bp.git_repository)
336 self.assertIsNotNone(bp.broken_git_commit_sha1)
337 self.assertIsNotNone(bp.fixed_git_commit_sha1)
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index bd09864..dee8312 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -48,6 +48,7 @@ from lp.app.errors import (
48from lp.app.interfaces.informationtype import IInformationType48from lp.app.interfaces.informationtype import IInformationType
49from lp.app.interfaces.launchpad import ILaunchpadCelebrities49from lp.app.interfaces.launchpad import ILaunchpadCelebrities
50from lp.app.interfaces.services import IService50from lp.app.interfaces.services import IService
51from lp.bugs.interfaces.bugpresence import IBugPresenceSet
51from lp.charms.interfaces.charmrecipe import ICharmRecipeSet52from lp.charms.interfaces.charmrecipe import ICharmRecipeSet
52from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta53from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta
53from lp.code.enums import (54from lp.code.enums import (
@@ -1955,6 +1956,15 @@ class GitRepository(
1955 )1956 )
1956 for recipe in recipes1957 for recipe in recipes
1957 )1958 )
1959 bug_presences = getUtility(IBugPresenceSet).getByTarget(self)
1960 deletion_operations.extend(
1961 DeletionCallable(
1962 bug_presence,
1963 msg("This bug presence refers to this repository."),
1964 bug_presence.destroySelf,
1965 )
1966 for bug_presence in bug_presences
1967 )
1958 if not getUtility(ISnapSet).findByGitRepository(self).is_empty():1968 if not getUtility(ISnapSet).findByGitRepository(self).is_empty():
1959 alteration_operations.append(1969 alteration_operations.append(
1960 DeletionCallable(1970 DeletionCallable(
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index b95c374..e43d9fc 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -65,6 +65,7 @@ from lp.blueprints.interfaces.specification import ISpecificationSet
65from lp.blueprints.interfaces.sprint import ISprintSet65from lp.blueprints.interfaces.sprint import ISprintSet
66from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource66from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
67from lp.bugs.interfaces.bug import CreateBugParams, IBugSet67from lp.bugs.interfaces.bug import CreateBugParams, IBugSet
68from lp.bugs.interfaces.bugpresence import IBugPresenceSet
68from lp.bugs.interfaces.bugtask import (69from lp.bugs.interfaces.bugtask import (
69 BugTaskImportance,70 BugTaskImportance,
70 BugTaskStatus,71 BugTaskStatus,
@@ -2652,6 +2653,38 @@ class LaunchpadObjectFactory(ObjectFactory):
2652 )2653 )
2653 )2654 )
26542655
2656 def makeBugPresence(
2657 self,
2658 bug=None,
2659 target=None,
2660 broken_version=None,
2661 fixed_version=None,
2662 broken_git_commit_sha1=None,
2663 fixed_git_commit_sha1=None,
2664 ):
2665 if bug is None:
2666 bug = self.makeBug()
2667 if target is None:
2668 target = self.makeProduct()
2669
2670 if IGitRepository.providedBy(target):
2671 if not broken_git_commit_sha1 and not fixed_git_commit_sha1:
2672 broken_git_commit_sha1 = self.getUniqueHexString(40)
2673 fixed_git_commit_sha1 = self.getUniqueHexString(40)
2674
2675 elif not broken_version and not fixed_version:
2676 broken_version = self.getUniqueString("version")
2677 fixed_version = self.getUniqueString("version")
2678
2679 return getUtility(IBugPresenceSet).new(
2680 bug=bug,
2681 target=target,
2682 broken_version=broken_version,
2683 fixed_version=fixed_version,
2684 broken_git_commit_sha1=broken_git_commit_sha1,
2685 fixed_git_commit_sha1=fixed_git_commit_sha1,
2686 )
2687
2655 def makeSignedMessage(2688 def makeSignedMessage(
2656 self,2689 self,
2657 msgid=None,2690 msgid=None,

Subscribers

People subscribed via source and target branches

to status/vote changes: