Compare commits

...

4 Commits

14 changed files with 116 additions and 89 deletions

View File

@@ -20,7 +20,7 @@ class ListDetailAPITest(BaseAPITest):
response = self.client.get(f"/api/lists/{list_.id}/") response = self.client.get(f"/api/lists/{list_.id}/")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["id"], list_.id) self.assertEqual(response.data["id"], str(list_.id))
self.assertEqual(len(response.data["items"]), 2) self.assertEqual(len(response.data["items"]), 2)
class ListItemsAPITest(BaseAPITest): class ListItemsAPITest(BaseAPITest):
@@ -48,7 +48,7 @@ class ListsAPITest(BaseAPITest):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1) self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["id"], list1.id) self.assertEqual(response.data[0]["id"], str(list1.id))
def test_post_creates_list_with_item(self): def test_post_creates_list_with_item(self):
response = self.client.post( response = self.client.post(

View File

@@ -5,7 +5,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.ListsAPI.as_view(), name='api_lists'), path('', views.ListsAPI.as_view(), name='api_lists'),
path('<int:list_id>/', views.ListDetailAPI.as_view(), name='api_list_detail'), path('<uuid:list_id>/', views.ListDetailAPI.as_view(), name='api_list_detail'),
path('<int:list_id>/items/', views.ListItemsAPI.as_view(), name='api_list_items'), path('<uuid:list_id>/items/', views.ListItemsAPI.as_view(), name='api_list_items'),
] ]

View File

@@ -1,6 +1,8 @@
# Generated by Django 6.0 on 2026-02-08 01:19 # Generated by Django 6.0 on 2026-02-23 04:30
import django.db.models.deletion import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -9,13 +11,16 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='List', name='List',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL)),
('shared_with', models.ManyToManyField(blank=True, related_name='shared_lists', to=settings.AUTH_USER_MODEL)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(

View File

@@ -1,21 +0,0 @@
# Generated by Django 6.0 on 2026-02-09 03:29
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='list',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 6.0 on 2026-02-18 18:13
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0002_list_owner'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='list',
name='shared_with',
field=models.ManyToManyField(blank=True, related_name='shared_lists', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -1,7 +1,11 @@
import uuid
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
class List(models.Model): class List(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey( owner = models.ForeignKey(
"lyric.User", "lyric.User",
related_name="lists", related_name="lists",

View File

@@ -43,7 +43,7 @@ class ItemModelTest(TestCase):
class ListModelTest(TestCase): class ListModelTest(TestCase):
def test_get_absolute_url(self): def test_get_absolute_url(self):
mylist = List.objects.create() mylist = List.objects.create()
self.assertEqual(mylist.get_absolute_url(), f"/apps/dashboard/{mylist.id}/") self.assertEqual(mylist.get_absolute_url(), f"/dashboard/list/{mylist.id}/")
def test_list_items_order(self): def test_list_items_order(self):
list1 = List.objects.create() list1 = List.objects.create()

View File

@@ -1,7 +1,7 @@
import lxml.html import lxml.html
from unittest import skip
from django.test import TestCase from django.test import TestCase
from django.urls import reverse
from django.utils import html from django.utils import html
from apps.dashboard.forms import ( from apps.dashboard.forms import (
@@ -21,26 +21,26 @@ class HomePageTest(TestCase):
response = self.client.get('/') response = self.client.get('/')
parsed = lxml.html.fromstring(response.content) parsed = lxml.html.fromstring(response.content)
forms = parsed.cssselect('form[method=POST]') forms = parsed.cssselect('form[method=POST]')
self.assertIn("/apps/dashboard/new_list", [form.get("action") for form in forms]) self.assertIn("/dashboard/new_list", [form.get("action") for form in forms])
[form] = [form for form in forms if form.get("action") == "/apps/dashboard/new_list"] [form] = [form for form in forms if form.get("action") == "/dashboard/new_list"]
inputs = form.cssselect("input") inputs = form.cssselect("input")
self.assertIn("text", [input.get("name") for input in inputs]) self.assertIn("text", [input.get("name") for input in inputs])
class NewListTest(TestCase): class NewListTest(TestCase):
def test_can_save_a_POST_request(self): def test_can_save_a_POST_request(self):
self. client.post("/apps/dashboard/new_list", data={"text": "A new list item"}) self. client.post("/dashboard/new_list", data={"text": "A new list item"})
self.assertEqual(Item.objects.count(), 1) self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.get() new_item = Item.objects.get()
self.assertEqual(new_item.text, "A new list item") self.assertEqual(new_item.text, "A new list item")
def test_redirects_after_POST(self): def test_redirects_after_POST(self):
response = self.client.post("/apps/dashboard/new_list", data={"text": "A new list item"}) response = self.client.post("/dashboard/new_list", data={"text": "A new list item"})
new_list = List.objects.get() new_list = List.objects.get()
self.assertRedirects(response, f"/apps/dashboard/{new_list.id}/") self.assertRedirects(response, f"/dashboard/list/{new_list.id}/")
# Post invalid input helper # Post invalid input helper
def post_invalid_input(self): def post_invalid_input(self):
return self.client.post("/apps/dashboard/new_list", data={"text": ""}) return self.client.post("/dashboard/new_list", data={"text": ""})
def test_for_invalid_input_nothing_saved_to_db(self): def test_for_invalid_input_nothing_saved_to_db(self):
self.post_invalid_input() self.post_invalid_input()
@@ -58,12 +58,12 @@ class NewListTest(TestCase):
class ListViewTest(TestCase): class ListViewTest(TestCase):
def test_uses_list_template(self): def test_uses_list_template(self):
mylist = List.objects.create() mylist = List.objects.create()
response = self.client.get(f"/apps/dashboard/{mylist.id}/") response = self.client.get(f"/dashboard/list/{mylist.id}/")
self.assertTemplateUsed(response, "apps/dashboard/list.html") self.assertTemplateUsed(response, "apps/dashboard/list.html")
def test_renders_input_form(self): def test_renders_input_form(self):
mylist = List.objects.create() mylist = List.objects.create()
url = f"/apps/dashboard/{mylist.id}/" url = f"/dashboard/list/{mylist.id}/"
response = self.client.get(url) response = self.client.get(url)
parsed = lxml.html.fromstring(response.content) parsed = lxml.html.fromstring(response.content)
forms = parsed.cssselect("form[method=POST]") forms = parsed.cssselect("form[method=POST]")
@@ -80,7 +80,7 @@ class ListViewTest(TestCase):
other_list = List.objects.create() other_list = List.objects.create()
Item.objects.create(text="other list item", list=other_list) Item.objects.create(text="other list item", list=other_list)
# When/Act # When/Act
response = self.client.get(f"/apps/dashboard/{correct_list.id}/") response = self.client.get(f"/dashboard/list/{correct_list.id}/")
# Then/Assert # Then/Assert
self.assertContains(response, "itemey 1") self.assertContains(response, "itemey 1")
self.assertContains(response, "itemey 2") self.assertContains(response, "itemey 2")
@@ -91,7 +91,7 @@ class ListViewTest(TestCase):
correct_list = List.objects.create() correct_list = List.objects.create()
self.client.post( self.client.post(
f"/apps/dashboard/{correct_list.id}/", f"/dashboard/list/{correct_list.id}/",
data={"text": "A new item for an existing list"}, data={"text": "A new item for an existing list"},
) )
@@ -105,16 +105,16 @@ class ListViewTest(TestCase):
correct_list = List.objects.create() correct_list = List.objects.create()
response = self.client.post( response = self.client.post(
f"/apps/dashboard/{correct_list.id}/", f"/dashboard/list/{correct_list.id}/",
data={"text": "A new item for an existing list"}, data={"text": "A new item for an existing list"},
) )
self.assertRedirects(response, f"/apps/dashboard/{correct_list.id}/") self.assertRedirects(response, f"/dashboard/list/{correct_list.id}/")
# Post invalid input helper # Post invalid input helper
def post_invalid_input(self): def post_invalid_input(self):
mylist = List.objects.create() mylist = List.objects.create()
return self.client.post(f"/apps/dashboard/{mylist.id}/", data={"text": ""}) return self.client.post(f"/dashboard/list/{mylist.id}/", data={"text": ""})
def test_for_invalid_input_nothing_saved_to_db(self): def test_for_invalid_input_nothing_saved_to_db(self):
self.post_invalid_input() self.post_invalid_input()
@@ -140,7 +140,7 @@ class ListViewTest(TestCase):
Item.objects.create(list=list1, text="lorem ipsum") Item.objects.create(list=list1, text="lorem ipsum")
response = self.client.post( response = self.client.post(
f"/apps/dashboard/{list1.id}/", f"/dashboard/list/{list1.id}/",
data={"text": "lorem ipsum"}, data={"text": "lorem ipsum"},
) )
@@ -153,26 +153,26 @@ class MyListsTest(TestCase):
def test_my_lists_url_renders_my_lists_template(self): def test_my_lists_url_renders_my_lists_template(self):
user = User.objects.create(email="a@b.cde") user = User.objects.create(email="a@b.cde")
self.client.force_login(user) self.client.force_login(user)
response = self.client.get(f"/apps/dashboard/users/{user.id}/") response = self.client.get(f"/dashboard/users/{user.id}/")
self.assertTemplateUsed(response, "apps/dashboard/my_lists.html") self.assertTemplateUsed(response, "apps/dashboard/my_lists.html")
def test_passes_correct_owner_to_template(self): def test_passes_correct_owner_to_template(self):
User.objects.create(email="wrongowner@example.com") User.objects.create(email="wrongowner@example.com")
correct_user = User.objects.create(email="a@b.cde") correct_user = User.objects.create(email="a@b.cde")
self.client.force_login(correct_user) self.client.force_login(correct_user)
response = self.client.get(f"/apps/dashboard/users/{correct_user.id}/") response = self.client.get(f"/dashboard/users/{correct_user.id}/")
self.assertEqual(response.context["owner"], correct_user) self.assertEqual(response.context["owner"], correct_user)
def test_list_owner_is_saved_if_user_is_authenticated(self): def test_list_owner_is_saved_if_user_is_authenticated(self):
user = User.objects.create(email="a@b.cde") user = User.objects.create(email="a@b.cde")
self.client.force_login(user) self.client.force_login(user)
self.client.post("/apps/dashboard/new_list", data={"text": "new item"}) self.client.post("/dashboard/new_list", data={"text": "new item"})
new_list = List.objects.get() new_list = List.objects.get()
self.assertEqual(new_list.owner, user) self.assertEqual(new_list.owner, user)
def test_my_lists_redirects_if_not_logged_in(self): def test_my_lists_redirects_if_not_logged_in(self):
user = User.objects.create(email="a@b.cde") user = User.objects.create(email="a@b.cde")
response = self.client.get(f"/apps/dashboard/users/{user.id}/") response = self.client.get(f"/dashboard/users/{user.id}/")
self.assertRedirects(response, "/") self.assertRedirects(response, "/")
def test_my_lists_returns_403_for_wrong_user(self): def test_my_lists_returns_403_for_wrong_user(self):
@@ -180,7 +180,7 @@ class MyListsTest(TestCase):
user1 = User.objects.create(email="a@b.cde") user1 = User.objects.create(email="a@b.cde")
user2 = User.objects.create(email="wrongowner@example.com") user2 = User.objects.create(email="wrongowner@example.com")
self.client.force_login(user2) self.client.force_login(user2)
response = self.client.get(f"/apps/dashboard/users/{user1.id}/") response = self.client.get(f"/dashboard/users/{user1.id}/")
# assert 403 # assert 403
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@@ -189,16 +189,16 @@ class ShareListTest(TestCase):
our_list = List.objects.create() our_list = List.objects.create()
alice = User.objects.create(email="alice@example.com") alice = User.objects.create(email="alice@example.com")
response = self.client.post( response = self.client.post(
f"/apps/dashboard/{our_list.id}/share_list", f"/dashboard/list/{our_list.id}/share_list",
data={"recipient": "alice@example.com"}, data={"recipient": "alice@example.com"},
) )
self.assertRedirects(response, f"/apps/dashboard/{our_list.id}/") self.assertRedirects(response, f"/dashboard/list/{our_list.id}/")
def test_post_with_email_adds_user_to_shared_with(self): def test_post_with_email_adds_user_to_shared_with(self):
our_list = List.objects.create() our_list = List.objects.create()
alice = User.objects.create(email="alice@example.com") alice = User.objects.create(email="alice@example.com")
self.client.post( self.client.post(
f"/apps/dashboard/{our_list.id}/share_list", f"/dashboard/list/{our_list.id}/share_list",
data={"recipient": "alice@example.com"}, data={"recipient": "alice@example.com"},
) )
self.assertIn(alice, our_list.shared_with.all()) self.assertIn(alice, our_list.shared_with.all())
@@ -206,7 +206,37 @@ class ShareListTest(TestCase):
def test_post_with_nonexistent_email_redirects_to_list(self): def test_post_with_nonexistent_email_redirects_to_list(self):
our_list = List.objects.create() our_list = List.objects.create()
response = self.client.post( response = self.client.post(
f"/apps/dashboard/{our_list.id}/share_list", f"/dashboard/list/{our_list.id}/share_list",
data={"recipient": "nobody@example.com"}, data={"recipient": "nobody@example.com"},
) )
self.assertRedirects(response, f"/apps/dashboard/{our_list.id}/") self.assertRedirects(response, f"/dashboard/list/{our_list.id}/")
def test_share_list_does_not_add_owner_as_recipient(self):
owner = User.objects.create(email="owner@example.com")
our_list = List.objects.create(owner=owner)
self.client.force_login(owner)
self.client.post(reverse("share_list", args=[our_list.id]),
data={"recipient": "owner@example.com"})
self.assertNotIn(owner, our_list.shared_with.all())
class ViewAuthListTest(TestCase):
def setUp(self):
self.owner = User.objects.create(email="disco@example.com")
self.our_list = List.objects.create(owner=self.owner)
def test_anonymous_user_is_redirected(self):
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
self.assertRedirects(response, "/")
def test_non_owner_non_shared_user_gets_403(self):
stranger = User.objects.create(email="stranger@example.com")
self.client.force_login(stranger)
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
self.assertEqual(response.status_code, 403)
def test_shared_with_user_can_access_list(self):
guest = User.objects.create(email="guest@example.com")
self.our_list.shared_with.add(guest)
self.client.force_login(guest)
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
self.assertEqual(response.status_code, 200)

View File

@@ -3,7 +3,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path('new_list', views.new_list, name='new_list'), path('new_list', views.new_list, name='new_list'),
path('<int:list_id>/', views.view_list, name='view_list'), path('list/<uuid:list_id>/', views.view_list, name='view_list'),
path('users/<uuid:user_id>/', views.my_lists, name='my_lists'), path('users/<uuid:user_id>/', views.my_lists, name='my_lists'),
path('<int:list_id>/share_list', views.share_list, name="share_list"), path('list/<uuid:list_id>/share_list', views.share_list, name="share_list"),
] ]

View File

@@ -23,6 +23,13 @@ def new_list(request):
def view_list(request, list_id): def view_list(request, list_id):
our_list = List.objects.get(id=list_id) our_list = List.objects.get(id=list_id)
if our_list.owner:
if not request.user.is_authenticated:
return redirect("/")
if request.user != our_list.owner and request.user not in our_list.shared_with.all():
return HttpResponseForbidden()
form = ExistingListItemForm(for_list=our_list) form = ExistingListItemForm(for_list=our_list)
if request.method == "POST": if request.method == "POST":
@@ -44,6 +51,8 @@ def share_list(request, list_id):
our_list = List.objects.get(id=list_id) our_list = List.objects.get(id=list_id)
try: try:
recipient = User.objects.get(email=request.POST["recipient"]) recipient = User.objects.get(email=request.POST["recipient"])
if recipient == request.user:
return redirect(our_list)
our_list.shared_with.add(recipient) our_list.shared_with.add(recipient)
except User.DoesNotExist: except User.DoesNotExist:
pass pass

View File

@@ -9,13 +9,13 @@ from apps.lyric.models import Token
class SendLoginEmailViewTest(TestCase): class SendLoginEmailViewTest(TestCase):
def test_redirects_to_home_page(self, mock_delay): def test_redirects_to_home_page(self, mock_delay):
response = self.client.post( response = self.client.post(
"/apps/lyric/send_login_email", data={"email": "discoman@example.com"} "/lyric/send_login_email", data={"email": "discoman@example.com"}
) )
self.assertRedirects(response, "/") self.assertRedirects(response, "/")
def test_sends_mail_to_address_from_post(self, mock_delay): def test_sends_mail_to_address_from_post(self, mock_delay):
self.client.post( self.client.post(
"/apps/lyric/send_login_email", data={"email": "discoman@example.com"} "/lyric/send_login_email", data={"email": "discoman@example.com"}
) )
self.assertEqual(mock_delay.called, True) self.assertEqual(mock_delay.called, True)
@@ -23,7 +23,7 @@ class SendLoginEmailViewTest(TestCase):
def test_adds_success_message(self, mock_delay): def test_adds_success_message(self, mock_delay):
response = self.client.post( response = self.client.post(
"/apps/lyric/send_login_email", "/lyric/send_login_email",
data={"email": "discoman@example.com"}, data={"email": "discoman@example.com"},
follow=True follow=True
) )
@@ -37,23 +37,23 @@ class SendLoginEmailViewTest(TestCase):
def test_creates_token_associated_with_email(self, mock_delay): def test_creates_token_associated_with_email(self, mock_delay):
self.client.post( self.client.post(
"/apps/lyric/send_login_email", data={"email": "discoman@example.com"} "/lyric/send_login_email", data={"email": "discoman@example.com"}
) )
token = Token.objects.get() token = Token.objects.get()
self.assertEqual(token.email, "discoman@example.com") self.assertEqual(token.email, "discoman@example.com")
def test_sends_link_to_login_using_token_uid(self, mock_delay): def test_sends_link_to_login_using_token_uid(self, mock_delay):
self.client.post( self.client.post(
"/apps/lyric/send_login_email", data={"email": "discoman@example.com"} "/lyric/send_login_email", data={"email": "discoman@example.com"}
) )
token = Token.objects.get() token = Token.objects.get()
expected_url = f"http://testserver/apps/lyric/login?token={token.uid}" expected_url = f"http://testserver/lyric/login?token={token.uid}"
self.assertEqual(mock_delay.call_args.args[1], expected_url) self.assertEqual(mock_delay.call_args.args[1], expected_url)
class LoginViewTest(TestCase): class LoginViewTest(TestCase):
def test_redirects_to_home_page(self): def test_redirects_to_home_page(self):
response = self.client.get("/apps/lyric/login?token=abc123") response = self.client.get("/lyric/login?token=abc123")
self.assertRedirects(response, "/") self.assertRedirects(response, "/")
def test_logs_in_if_given_valid_token(self): def test_logs_in_if_given_valid_token(self):
@@ -61,14 +61,14 @@ class LoginViewTest(TestCase):
self.assertEqual(anon_user.is_authenticated, False) self.assertEqual(anon_user.is_authenticated, False)
token = Token.objects.create(email="discoman@example.com") token = Token.objects.create(email="discoman@example.com")
self.client.get(f"/apps/lyric/login?token={token.uid}", follow=True) self.client.get(f"/lyric/login?token={token.uid}", follow=True)
user = auth.get_user(self.client) user = auth.get_user(self.client)
self.assertEqual(user.is_authenticated, True) self.assertEqual(user.is_authenticated, True)
self.assertEqual(user.email, "discoman@example.com") self.assertEqual(user.email, "discoman@example.com")
def test_shows_login_error_if_token_invalid(self): def test_shows_login_error_if_token_invalid(self):
response = self.client.get("/apps/lyric/login?token=invalid-token", follow=True) response = self.client.get("/lyric/login?token=invalid-token", follow=True)
user = auth.get_user(self.client) user = auth.get_user(self.client)
self.assertEqual(user.is_authenticated, False) self.assertEqual(user.is_authenticated, False)
message = list(response.context["messages"])[0] message = list(response.context["messages"])[0]
@@ -80,7 +80,7 @@ class LoginViewTest(TestCase):
@mock.patch("apps.lyric.views.auth") @mock.patch("apps.lyric.views.auth")
def test_calls_authenticate_with_uid_from_get_request(self, mock_auth): def test_calls_authenticate_with_uid_from_get_request(self, mock_auth):
self.client.get("/apps/lyric/login?token=abc123") self.client.get("/lyric/login?token=abc123")
self.assertEqual( self.assertEqual(
mock_auth.authenticate.call_args, mock_auth.authenticate.call_args,
mock.call(uid="abc123") mock.call(uid="abc123")

View File

@@ -6,8 +6,8 @@ from apps.dashboard import views as dash_views
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', dash_views.home_page, name='home'), path('', dash_views.home_page, name='home'),
path('apps/dashboard/', include('apps.dashboard.urls')), path('dashboard/', include('apps.dashboard.urls')),
path('apps/lyric/', include('apps.lyric.urls')), path('lyric/', include('apps.lyric.urls')),
path('api/lists/', include('apps.api.urls')), path('api/lists/', include('apps.api.urls')),
] ]

View File

@@ -1,5 +1,6 @@
import os import os
from django.conf import settings
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@@ -57,3 +58,16 @@ class SharingTest(FunctionalTest):
self.browser = disco_browser self.browser = disco_browser
self.browser.refresh() self.browser.refresh()
list_page.wait_for_row_in_list_table("At your command, Disco King", 2) list_page.wait_for_row_in_list_table("At your command, Disco King", 2)
class ListAccessTest(FunctionalTest):
def test_stranger_cannot_access_owned_list(self):
self.create_pre_authenticated_session("disco@example.com")
self.browser.get(self.live_server_url)
list_page = ListPage(self).add_list_item("private eye")
list_url = self.browser.current_url
self.browser.delete_cookie(settings.SESSION_COOKIE_NAME)
self.browser.get(list_url)
self.assertNotEqual(self.browser.current_url, list_url)

View File

@@ -34,7 +34,10 @@ class NewVisitorTest(FunctionalTest):
list_page.add_list_item("Buy peacock feathers") list_page.add_list_item("Buy peacock feathers")
edith_dash_url = self.browser.current_url edith_dash_url = self.browser.current_url
self.assertRegex(edith_dash_url, '/apps/dashboard/.+') self.assertRegex(
edith_dash_url,
r'/dashboard/list/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
)
self.browser.delete_all_cookies() self.browser.delete_all_cookies()
@@ -46,7 +49,10 @@ class NewVisitorTest(FunctionalTest):
list_page.add_list_item("Buy milk") list_page.add_list_item("Buy milk")
francis_dash_url = self.browser.current_url francis_dash_url = self.browser.current_url
self.assertRegex(francis_dash_url, '/apps/dashboard/.+') self.assertRegex(
francis_dash_url,
r'/dashboard/list/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
)
self.assertNotEqual(francis_dash_url, edith_dash_url) self.assertNotEqual(francis_dash_url, edith_dash_url)
page_text = self.browser.find_element(By.TAG_NAME, 'body').text page_text = self.browser.find_element(By.TAG_NAME, 'body').text