1212coverage incrementally. See #1267 for the broader plan.
1313"""
1414
15- from django .core .files .uploadedfile import SimpleUploadedFile
16- from django .test import TestCase
15+ from datetime import date as _date
1716
17+ from django .test import TestCase
1818
19- # Minimal 1x1 GIF used to satisfy Person.image / Person.easter_egg without
20- # touching the filesystem. Person.save() picks a random Star Wars image when
21- # either field is empty, opening a real file from media/. Pre-setting both
22- # with this SimpleUploadedFile skips the fallback branch entirely.
23- _GIF_1PX = (
24- b"GIF89a \x01 \x00 \x01 \x00 \x80 \x00 \x00 \xff \xff \xff \x00 \x00 \x00 "
25- b"! \xf9 \x04 \x01 \x00 \x00 \x00 \x00 , \x00 \x00 \x00 \x00 \x01 \x00 \x01 "
26- b" \x00 \x00 \x02 \x02 D \x01 \x00 ;"
19+ from website . tests . factories import (
20+ NewsItemFactory ,
21+ PersonFactory ,
22+ ProjectFactory ,
23+ PublicationFactory ,
24+ TalkFactory ,
25+ VideoFactory ,
26+ image_upload ,
2727)
2828
2929
30- def _make_image_upload (name ):
31- """Return a SimpleUploadedFile that satisfies an ImageField."""
32- return SimpleUploadedFile (name , _GIF_1PX , content_type = "image/gif" )
33-
34-
3530class DatabaseTestCase (TestCase ):
3631 """
3732 Shared base for tests that touch the database. Provides small fixture
38- helpers (make_person / make_publication / make_news_item) built on
39- plain Model.objects.create() — no third-party fixture library. Each
40- test runs inside a transaction and is rolled back, so tests stay
41- isolated without manual cleanup.
42-
43- Why a base class instead of module-level helpers: subclasses can
44- override the defaults in setUp() and the helpers can grow without
45- cluttering the module namespace.
33+ helpers (make_person / make_publication / make_news_item / ...) that
34+ delegate to the factory_boy factories in :mod:`website.tests.factories`
35+ (#1272). The factories are the single source of truth for building
36+ instances; these helpers preserve the original keyword API (notably the
37+ ``year`` shorthand) so the existing suite keeps working unchanged. Each
38+ test runs inside a transaction and is rolled back, so tests stay isolated
39+ without manual cleanup.
40+
41+ Why keep the helpers at all: they encode test-friendly defaults (fixed
42+ dates via ``year``, ``with_thumbnail`` for the project visibility backfill)
43+ and give subclasses a stable seam to override in ``setUp()``. Tests that
44+ want richer fixtures (Faker values, batches, the relationship graph) can
45+ import and use the factories directly.
4646 """
4747
4848 def make_person (self , first_name = "Jane" , last_name = "Doe" , ** kwargs ):
4949 """
50- Create and return a Person. Image fields are pre-populated to
51- skip Person.save()'s Star Wars fallback (which reads a real file
52- from media/). Override by passing image=... explicitly.
50+ Create and return a Person. Image fields are pre-populated by
51+ PersonFactory to skip Person.save()'s Star Wars fallback (which reads a
52+ real file from media/). Override by passing image=... explicitly.
5353 """
54- from website .models import Person
55- kwargs .setdefault (
56- "image" , _make_image_upload (f"{ first_name } _{ last_name } .gif" )
57- )
58- kwargs .setdefault (
59- "easter_egg" ,
60- _make_image_upload (f"{ first_name } _{ last_name } _egg.gif" ),
61- )
62- return Person .objects .create (
54+ return PersonFactory (
6355 first_name = first_name , last_name = last_name , ** kwargs
6456 )
6557
6658 def make_publication (self , title = "A Test Paper" , year = 2024 , ** kwargs ):
6759 """
6860 Create and return a Publication with sensible defaults: post-lab-
69- formation date, conference venue, a forum name, and a dummy PDF
70- (display_pub_snippet.html unconditionally renders pub.pdf_file.url,
71- so tests that go through the publications view need one to render).
72- Override via kwargs.
61+ formation date (from ``year``) , conference venue, a forum name, and a
62+ dummy PDF (display_pub_snippet.html unconditionally renders
63+ pub.pdf_file.url, so tests that go through the publications view need
64+ one to render). Override via kwargs.
7365 """
74- from datetime import date as _date
75- from website .models import Publication
76- from website .models .publication import PubType
7766 kwargs .setdefault ("date" , _date (year , 1 , 1 ))
78- kwargs .setdefault ("forum_name" , "CHI" )
79- kwargs .setdefault ("pub_venue_type" , PubType .CONFERENCE )
80- kwargs .setdefault (
81- "pdf_file" ,
82- SimpleUploadedFile (
83- f"{ title .replace (' ' , '_' )} .pdf" ,
84- b"%PDF-1.4 test" ,
85- content_type = "application/pdf" ,
86- ),
87- )
88- return Publication .objects .create (title = title , ** kwargs )
67+ return PublicationFactory (title = title , ** kwargs )
8968
9069 def make_talk (self , title = "A Test Talk" , year = 2024 , ** kwargs ):
9170 """
9271 Create and return a Talk. Artifact.save() generates a thumbnail
93- from pdf_file (via ImageMagick) on every save, so we provide a
94- small valid PDF and let it run; tests that don't care about the
72+ from pdf_file (via ImageMagick) on every save, so the factory provides
73+ a small valid PDF and lets it run; tests that don't care about the
9574 thumbnail just ignore it.
9675 """
97- from datetime import date as _date
98- from website .models import Talk
99- from website .models .talk import TalkType
10076 kwargs .setdefault ("date" , _date (year , 1 , 1 ))
101- kwargs .setdefault ("forum_name" , "CHI" )
102- kwargs .setdefault ("talk_type" , TalkType .CONFERENCE_TALK )
103- kwargs .setdefault (
104- "pdf_file" ,
105- SimpleUploadedFile (
106- f"{ title .replace (' ' , '_' )} .pdf" ,
107- b"%PDF-1.4 test" ,
108- content_type = "application/pdf" ,
109- ),
110- )
111- return Talk .objects .create (title = title , ** kwargs )
77+ return TalkFactory (title = title , ** kwargs )
11278
11379 def make_video (self , title = "A Test Video" , year = 2024 , ** kwargs ):
11480 """
@@ -117,23 +83,18 @@ def make_video(self, title="A Test Video", year=2024, **kwargs):
11783 would raise), and the video snippet embeds it. date is set so
11884 get_most_recent_artifact_date() has something to sort on.
11985 """
120- from datetime import date as _date
121- from website .models import Video
12286 kwargs .setdefault ("date" , _date (year , 1 , 1 ))
123- kwargs .setdefault ("video_url" , "https://www.youtube.com/watch?v=dQw4w9WgXcQ" )
124- return Video .objects .create (title = title , ** kwargs )
87+ return VideoFactory (title = title , ** kwargs )
12588
12689 def make_news_item (self , title = "Test News" , author = None , ** kwargs ):
12790 """
12891 Create and return a News item. `author` is intentionally optional
12992 (the FK is nullable with on_delete=SET_NULL) so tests can exercise
13093 the authorless code path that caused the original /news/158/ bug.
13194 """
132- from datetime import date as _date
133- from website .models import News
13495 kwargs .setdefault ("date" , _date (2024 , 1 , 1 ))
13596 kwargs .setdefault ("content" , "Test news body." )
136- return News . objects . create (title = title , author = author , ** kwargs )
97+ return NewsItemFactory (title = title , author = author , ** kwargs )
13798
13899 def make_project (self , name = "A Test Project" , short_name = None ,
139100 with_thumbnail = False , ** kwargs ):
@@ -149,12 +110,11 @@ def make_project(self, name="A Test Project", short_name=None,
149110 visibility backfill. Defaults to False to avoid touching the
150111 filesystem unnecessarily.
151112 """
152- from website .models import Project
153113 if short_name is None :
154114 short_name = name .lower ().replace (" " , "" )
155115 if with_thumbnail :
156116 kwargs .setdefault (
157117 "gallery_image" ,
158- _make_image_upload (f"{ short_name } _thumb.gif" ),
118+ image_upload (f"{ short_name } _thumb.gif" ),
159119 )
160- return Project . objects . create (name = name , short_name = short_name , ** kwargs )
120+ return ProjectFactory (name = name , short_name = short_name , ** kwargs )
0 commit comments