recognition: Recognition model w. grant_if_new; sky_save returns stargazer on first chart save — TDD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-22 02:13:29 -04:00
parent 83ce238a2f
commit be061f6bc2
5 changed files with 205 additions and 3 deletions

View File

@@ -0,0 +1,30 @@
# Generated by Django 6.0 on 2026-04-22 06:11
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('drama', '0003_alter_gameevent_verb'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Recognition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=60)),
('earned_at', models.DateTimeField()),
('palette', models.CharField(blank=True, max_length=60, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recognitions', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['earned_at'],
'unique_together': {('user', 'slug')},
},
),
]

View File

@@ -168,3 +168,28 @@ class ScrollPosition(models.Model):
def record(room, verb, actor=None, **data):
"""Record a game event in the drama log."""
return GameEvent.objects.create(room=room, actor=actor, verb=verb, data=data)
class Recognition(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="recognitions",
)
slug = models.SlugField(max_length=60)
earned_at = models.DateTimeField()
palette = models.CharField(max_length=60, null=True, blank=True)
class Meta:
unique_together = [("user", "slug")]
ordering = ["earned_at"]
def __str__(self):
return f"{self.user.email}{self.slug}"
@classmethod
def grant_if_new(cls, user, slug):
from django.utils import timezone
return cls.objects.get_or_create(
user=user, slug=slug,
defaults={"earned_at": timezone.now()},
)

View File

@@ -1,7 +1,8 @@
from django.test import TestCase
from django.db import IntegrityError
from django.utils import timezone
from apps.drama.models import GameEvent, ScrollPosition, record
from apps.drama.models import GameEvent, Recognition, ScrollPosition, record
from apps.epic.models import Room
from apps.lyric.models import User
@@ -173,3 +174,69 @@ class ScrollPositionModelTest(TestCase):
defaults={"position": 200},
)
self.assertEqual(ScrollPosition.objects.get(user=self.user, room=self.room).position, 200)
class RecognitionModelTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="earner@test.io")
def test_can_create_recognition(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
self.assertEqual(Recognition.objects.count(), 1)
self.assertEqual(recog.slug, "stargazer")
self.assertEqual(recog.user, self.user)
def test_palette_is_null_by_default(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
self.assertIsNone(recog.palette)
def test_palette_can_be_set(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
palette="palette-bardo",
)
self.assertEqual(recog.palette, "palette-bardo")
def test_unique_per_user_and_slug(self):
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
with self.assertRaises(IntegrityError):
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
def test_different_users_can_share_slug(self):
other = User.objects.create(email="other@test.io")
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
Recognition.objects.create(user=other, slug="stargazer", earned_at=timezone.now())
self.assertEqual(Recognition.objects.count(), 2)
def test_str_includes_slug_and_email(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
s = str(recog)
self.assertIn("stargazer", s)
self.assertIn("earner@test.io", s)
def test_grant_if_new_creates_on_first_call(self):
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertTrue(created)
self.assertEqual(recog.slug, "stargazer")
self.assertIsNotNone(recog.earned_at)
def test_grant_if_new_is_idempotent(self):
Recognition.grant_if_new(self.user, "stargazer")
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertFalse(created)
self.assertEqual(Recognition.objects.count(), 1)
def test_grant_if_new_does_not_overwrite_palette(self):
Recognition.objects.create(
user=self.user, slug="stargazer",
earned_at=timezone.now(), palette="palette-bardo",
)
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertFalse(created)
self.assertEqual(recog.palette, "palette-bardo")