Skip to content

Commit 611cbf0

Browse files
write write tests for slug, fix seed
1 parent 65981f7 commit 611cbf0

File tree

9 files changed

+122
-53
lines changed

9 files changed

+122
-53
lines changed

core/fixtures/programs.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,53 @@
22
pk: 1
33
fields:
44
name: TESTING
5-
slug: TESTING
65
is_closed: false
76
has_queue: true
87
- model: core.program
98
pk: 2
109
fields:
1110
name: CM
12-
slug: CM
1311
is_closed: false
1412
has_queue: true
1513
- model: core.program
1614
pk: 3
1715
fields:
1816
name: SSHP
19-
slug: SSHP
2017
is_closed: false
2118
has_queue: true
2219
- model: core.program
2320
pk: 4
2421
fields:
2522
name: LEGAL
26-
slug: LEGAL
2723
is_closed: false
2824
has_queue: true
2925
- model: core.program
3026
pk: 5
3127
fields:
3228
name: CRAFT
33-
slug: CRAFT
3429
is_closed: false
3530
has_queue: false
3631
- model: core.program
3732
pk: 6
3833
fields:
3934
name: PHAN
40-
slug: PHAN
4135
is_closed: false
4236
has_queue: false
4337
- model: core.program
4438
pk: 7
4539
fields:
4640
name: STEP
47-
slug: STEP
4841
is_closed: false
4942
has_queue: false
5043
- model: core.program
5144
pk: 8
5245
fields:
5346
name: BIENESTAR
54-
slug: BIENESTAR
5547
is_closed: false
5648
has_queue: false
5749
- model: core.program
5850
pk: 9
5951
fields:
6052
name: SKWC
61-
slug: SKWC
6253
is_closed: false
6354
has_queue: false

core/management/commands/seed.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,6 @@ def create_programs(output=True):
240240
for program, services in DEFAULT_PROGRAMS.items():
241241
p = Program()
242242
p.name = program
243-
p.slug = re.sub(r' [^a-zA-Z0-9\-]+', '',
244-
'-'.join(program.lower().split()))
245243

246244
if p.name in HAS_QUEUE:
247245
p.has_queue = True
@@ -256,8 +254,7 @@ def create_programs(output=True):
256254
for service in services:
257255
s = Service()
258256
s.name = service
259-
s.slug = p.slug+ '-'+re.sub(r' [^a-zA-Z0-9\-]',
260-
'', '-'.join(service.lower().split()))
257+
s.slug = f"{p.name}-{s.name}"
261258

262259
s.available = random_bool()
263260
s.program = p
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.13 on 2021-01-25 04:49
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('core', '0012_auto_20201216_0954'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='service',
15+
name='slug',
16+
field=models.CharField(max_length=100, unique=True, verbose_name='Concise Descriptive Identifier'),
17+
),
18+
]

core/programs/serializer.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ class ProgramSerializer(serializers.ModelSerializer):
99
class Meta:
1010
model = Program
1111
# related_name on model allows for services instead of service_set
12-
fields = ("id", "name", "slug", "is_closed",
13-
"is_frozen", "services", "has_queue")
14-
extra_kwargs = {
15-
"slug": {"read_only": True}
16-
}
12+
fields = ("id", "name", "is_closed", "is_frozen", "services", "has_queue")
13+
1714

1815

1916
class ProgramForVisitSerializer(serializers.ModelSerializer):
@@ -22,7 +19,4 @@ class ProgramForVisitSerializer(serializers.ModelSerializer):
2219
"""
2320
class Meta:
2421
model = Program
25-
fields = ("id", "name", "slug", "is_closed", "is_frozen", "has_queue")
26-
extra_kwargs = {
27-
"slug": {"read_only": True}
28-
}
22+
fields = ("id", "name", "is_closed", "is_frozen", "has_queue")

core/programs/views.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,3 @@
88
class ProgramViewSet(ModelViewSet):
99
queryset = Program.objects.all()
1010
serializer_class = ProgramSerializer
11-
12-
13-
def get_queryset(self):
14-
slug = self.request.query_params.get('slug', None)
15-
queryset = Program.objects.all()
16-
if slug is not None:
17-
queryset = queryset.filter(slug__iexact=slug)
18-
if not queryset.exists():
19-
raise NotFound()
20-
return queryset

core/services/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from core.models import Service
22
from core.viewsets import ModelViewSet
33
from core.services.serializers import ServiceSerializer
4+
from rest_framework.exceptions import NotFound
45

56
class ServiceViewSet(ModelViewSet):
67
queryset = Service.objects.all()

core/tests/services.py

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
from django.forms.models import model_to_dict
22
from core.tests.base import BaseTestCase
3+
from django.core.exceptions import ValidationError
34
from django.core.management import call_command
4-
from core.models import Service
5+
from core.models import Service, Program
56
from rest_framework import status
67
import random
78
import json
89

10+
911
def get_random_service():
10-
#gets random service from DB
12+
# gets random service from DB
1113
random_pk = random.randint(1, 40)
1214
return model_to_dict(Service.objects.get(pk=random_pk))
1315

14-
class ServicesTests(BaseTestCase):
15-
fixtures = ['services.yaml', 'programs.yaml']
1616

17+
class ServicesTests(BaseTestCase):
18+
fixtures = ["services.yaml", "programs.yaml"]
1719

1820
def test_get_services_list(self):
1921
"""
2022
Ensures that list of services is returned
2123
"""
22-
headers = self.auth_headers_for_user('admin')
23-
response = self.client.get( '/api/services/', follow=True, **headers)
24+
headers = self.auth_headers_for_user("admin")
25+
response = self.client.get("/api/services/", follow=True, **headers)
2426

2527
self.assertEqual(response.status_code, status.HTTP_200_OK)
2628
self.assertEqual(Service.objects.count(), 43)
@@ -29,15 +31,21 @@ def test_update_availability(self):
2931
"""
3032
Ensures the 'available' boolean can update
3133
"""
32-
headers = self.auth_headers_for_user('admin')
34+
headers = self.auth_headers_for_user("admin")
3335
random_service = get_random_service()
3436

35-
service_availability = random_service['available']
36-
random_service['available'] = not service_availability
37-
route = '/api/services/{}/'.format(random_service['id'])
37+
service_availability = random_service["available"]
38+
random_service["available"] = not service_availability
39+
route = "/api/services/{}/".format(random_service["id"])
3840

39-
response = self.client.put(route, json.dumps(random_service), content_type='application/json', follow=True, **headers)
40-
updated_availabilty = Service.objects.get(pk=random_service['id']).available
41+
response = self.client.put(
42+
route,
43+
json.dumps(random_service),
44+
content_type="application/json",
45+
follow=True,
46+
**headers
47+
)
48+
updated_availabilty = Service.objects.get(pk=random_service["id"]).available
4149

4250
self.assertNotEqual(service_availability, updated_availabilty)
4351
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -46,13 +54,80 @@ def test_disallow_service_name_change(self):
4654
"""
4755
Ensures that name field is read only
4856
"""
49-
headers = self.auth_headers_for_user('admin')
57+
headers = self.auth_headers_for_user("admin")
5058
random_service = get_random_service()
51-
service_name = random_service['name']
52-
random_service['name'] = 'Some nonexistent service'
53-
route = '/api/services/{}/'.format(random_service['id'])
59+
service_name = random_service["name"]
60+
random_service["name"] = "Some nonexistent service"
61+
route = "/api/services/{}/".format(random_service["id"])
5462

55-
self.client.put(route, json.dumps(random_service), content_type='application/json', follow=True, **headers)
56-
unchanged_name = Service.objects.get(pk=random_service['id']).name
57-
#returns a 200 status, but field is unchanged
63+
self.client.put(
64+
route,
65+
json.dumps(random_service),
66+
content_type="application/json",
67+
follow=True,
68+
**headers
69+
)
70+
unchanged_name = Service.objects.get(pk=random_service["id"]).name
71+
# returns a 200 status, but field is unchanged
5872
self.assertEqual(service_name, unchanged_name)
73+
74+
def test_service_has_slug_field(self):
75+
"""
76+
Ensure Service has slug field
77+
"""
78+
headers = self.auth_headers_for_user("admin")
79+
random_service = get_random_service()
80+
route = "/api/services/{}/".format(random_service["id"])
81+
res = self.client.get(route, follow=True, **headers)
82+
content = json.loads(res.content)
83+
self.assertTrue("slug" in content)
84+
self.assertTrue(content["slug"], random_service["slug"])
85+
86+
def test_disallow_slug_change(self):
87+
"""
88+
Ensure slug field read only
89+
"""
90+
headers = self.auth_headers_for_user("admin")
91+
random_service = get_random_service()
92+
service_slug = random_service["slug"]
93+
random_service["name"] = "a fake slug"
94+
route = "/api/services/{}/".format(random_service["id"])
95+
96+
self.client.put(
97+
route,
98+
json.dumps(random_service),
99+
content_type="application/json",
100+
follow=True,
101+
**headers
102+
)
103+
unchanged_slug = Service.objects.get(pk=random_service["id"]).name
104+
# returns a 200 status, but field is unchanged
105+
self.assertEqual(service_slug, unchanged_slug)
106+
107+
def test_slug_uniqueness(self):
108+
"""
109+
Ensure slug field is unique.
110+
Note: since serializer removes name and slug
111+
bc read_only=true for all request types at the moment,
112+
we're testing the model directly
113+
"""
114+
115+
slug = "banana"
116+
117+
program = Program.objects.all().first()
118+
Service.objects.create(name="some name", slug=slug, program=program)
119+
count_before_error = Service.objects.count()
120+
with self.assertRaises(Exception) as context:
121+
Service.objects.create(name="another name", slug=slug, program=program)
122+
123+
self.assertEqual(Service.objects.count(), count_before_error)
124+
125+
def test_slug_normalization(self):
126+
"""
127+
Ensure slug gets normalized when created
128+
"""
129+
130+
slug = " $%^$# 543 Sligh Bannaga *&^%*& "
131+
program = Program.objects.all().first()
132+
service = Service.objects.create(name="some name", slug=slug, program=program)
133+
self.assertRegex(service.slug, r"[a-z0-9\-]")

frontend/src/components/SepForm.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import PrevPointHeading from "./Typography/PrevPointHeading"
1515
import PrevPointCopy from "./Typography/PrevPointCopy"
1616
import { Formik, Form } from "formik"
1717
import { SEPSearchSchema, SEPNeedleSchema } from "../validation"
18-
import { SNACKBAR_SEVERITY } from "../constants"
18+
import { SNACKBAR_SEVERITY, SYRINGE_EXCHANGE_SLUG } from "../constants"
1919

2020
const useStyles = makeStyles(theme => ({
2121
root: {
@@ -78,7 +78,8 @@ const SepForm = ({
7878
}, [participantStore])
7979

8080
useEffect(() => {
81-
utilityStore.getServiceBySlug("sep-needle-exchange")
81+
utilityStore.getServiceBySlug(SYRINGE_EXCHANGE_SLUG)
82+
8283
return function cleanup() {
8384
utilityStore.setServiceSlugResponse({})
8485
}

frontend/src/constants/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ export const SNACKBAR_SEVERITY = {
5050
SUCCESS: "success",
5151
ERROR: "error",
5252
}
53+
54+
export const SYRINGE_EXCHANGE_SLUG = "sep-needle-exchange"

0 commit comments

Comments
 (0)