`Question.is_shared` defaulted to 1 and was only ever set by a route nothing called, so in practice it divided the bank into "everything" and "everything, plus your own private ones" — a distinction that cost every recommendation denominator a join and never changed an answer. Who may reach the bank is the site's own access rules; who may manage a question is the category grant tree. So the two predicates the whole bank was built on are now the same thing, and say what they actually mean: a question is out of reach if it has been deleted or belongs to a course. Nothing else. The column is dropped, the route that set it is gone, the bulk "share" action with it, and the Private tile and pill go from the question manager. The tests that turned on it have been rewritten rather than deleted, because the rule they were really about survives: revoking a question still revokes every session carrying it — by deleting it, which is the only revocation left. Several others named a category holding exactly two reachable questions and then answered two particular ids; that category holds four now, so they name the pair instead. A session's own sharing flag is untouched — that is a different thing, and it is still how a session is handed to somebody. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
93 lines
4.2 KiB
Python
93 lines
4.2 KiB
Python
"""Personal question libraries: what they report, and who may touch them.
|
|
|
|
Disposable SQLite, as everywhere in these tests. The rule the page depends on
|
|
is the ordering: most recently used first, and a library nobody has opened
|
|
sorts by when it was made rather than claiming a use that never happened.
|
|
"""
|
|
import unittest
|
|
from datetime import datetime
|
|
|
|
import test_quiz_builder as fixtures
|
|
from app.models.collection import UserCollection
|
|
from app.routers import collections
|
|
|
|
|
|
class CollectionTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.bank = fixtures.BuilderTests()
|
|
self.bank.setUp()
|
|
self.client = self.bank.client
|
|
self.db = self.bank.db
|
|
self.client.app.include_router(collections.router, prefix='/collections')
|
|
|
|
def tearDown(self):
|
|
self.bank.tearDown()
|
|
|
|
def make(self, title):
|
|
response = self.client.post('/collections/', json={'title': title})
|
|
self.assertEqual(response.status_code, 201, response.text)
|
|
return response.json()
|
|
|
|
def listed(self):
|
|
response = self.client.get('/collections/')
|
|
self.assertEqual(response.status_code, 200, response.text)
|
|
return response.json()
|
|
|
|
def test_a_new_library_reports_what_the_page_shows(self):
|
|
row = self.make('Cardiology misses')
|
|
self.assertEqual(row['title'], 'Cardiology misses')
|
|
self.assertEqual(row['question_count'], 0)
|
|
self.assertTrue(row['private'])
|
|
self.assertIsNone(row['last_used_at'])
|
|
self.assertIsNotNone(row['created_at'])
|
|
|
|
def test_adding_a_question_counts_it_and_marks_the_library_used(self):
|
|
row = self.make('Saved')
|
|
self.assertEqual(self.client.put(f"/collections/{row['id']}/questions/1").status_code, 200)
|
|
# The same question twice is one question.
|
|
self.assertFalse(self.client.put(f"/collections/{row['id']}/questions/1").json()['added'])
|
|
after = self.listed()[0]
|
|
self.assertEqual(after['question_count'], 1)
|
|
self.assertIsNotNone(after['last_used_at'])
|
|
|
|
def test_opening_a_library_is_using_it(self):
|
|
row = self.make('Saved')
|
|
self.assertIsNone(self.listed()[0]['last_used_at'])
|
|
self.assertEqual(self.client.get(f"/collections/{row['id']}/questions").status_code, 200)
|
|
self.assertIsNotNone(self.listed()[0]['last_used_at'])
|
|
|
|
def test_most_recently_used_first_and_the_unused_by_age(self):
|
|
from datetime import datetime
|
|
|
|
old = self.make('Older')
|
|
new = self.make('Newer')
|
|
used = self.make('Used long ago')
|
|
# An explicit clock: two rows made in the same second cannot be
|
|
# ordered by when they were made.
|
|
self.db.get(UserCollection, old['id']).created_at = datetime(2026, 1, 1)
|
|
self.db.get(UserCollection, new['id']).created_at = datetime(2026, 6, 1)
|
|
row = self.db.get(UserCollection, used['id'])
|
|
row.created_at = datetime(2025, 1, 1)
|
|
row.last_used_at = datetime(2026, 9, 1)
|
|
self.db.commit()
|
|
# Used beats made, and among the never-used the newer one is first.
|
|
self.assertEqual([c['title'] for c in self.listed()],
|
|
['Used long ago', 'Newer', 'Older'])
|
|
|
|
def test_a_library_belongs_to_one_person(self):
|
|
row = self.make('Mine')
|
|
self.bank.user = self.bank.peer
|
|
self.assertEqual(self.listed(), [])
|
|
self.assertEqual(self.client.patch(f"/collections/{row['id']}",
|
|
json={'title': 'Yours'}).status_code, 403)
|
|
self.assertEqual(self.client.delete(f"/collections/{row['id']}").status_code, 403)
|
|
self.assertEqual(self.client.get(f"/collections/{row['id']}/questions").status_code, 403)
|
|
|
|
def test_a_question_nobody_can_see_cannot_be_saved(self):
|
|
row = self.make('Mine')
|
|
# Question 4 has been removed from the bank; question 3 is an ordinary
|
|
# bank question and theirs to save.
|
|
self.bank.db.get(fixtures.Question, 4).deleted_at = datetime(2026, 1, 1)
|
|
self.bank.db.commit()
|
|
self.assertEqual(self.client.put(f"/collections/{row['id']}/questions/4").status_code, 404)
|
|
self.assertEqual(self.client.put(f"/collections/{row['id']}/questions/3").status_code, 200)
|