Coverage for server / services / growthbook / global_assignment_gate.py: 100%

24 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-10-04 09:33 +0000

1""" 

2Global assignment access checks for tiered practice globals (feature_key gating). 

3""" 

4 

5from typing import Any 

6 

7from fastapi import HTTPException, status 

8 

9from server.services.growthbook.context import TargetingContext 

10from server.services.growthbook.evaluation import feature_evaluation_service 

11from server.services.growthbook.registry import min_plan_for_feature, resolve_feature_key 

12 

13# Global assignments created by staff/admin/super-admin that are NOT part of the 

14# tiered exam catalog are gated behind this feature (Intermediate/Premium). 

15ERUDITION_TESTS_LIBRARY_FEATURE = "teacher.erudition_tests_library" 

16 

17 

18async def _erudition_tests_library_meta(ctx: TargetingContext) -> dict[str, Any]: 

19 enabled = await feature_evaluation_service.is_enabled( 

20 ERUDITION_TESTS_LIBRARY_FEATURE, ctx 

21 ) 

22 return { 

23 "feature_key": ERUDITION_TESTS_LIBRARY_FEATURE, 

24 "enabled": enabled, 

25 "locked": not enabled, 

26 "min_plan": min_plan_for_feature(ERUDITION_TESTS_LIBRARY_FEATURE), 

27 } 

28 

29 

30async def global_assignment_access_meta( 

31 assignment: dict[str, Any], 

32 ctx: TargetingContext, 

33) -> dict[str, Any]: 

34 """Return feature gating metadata for a global assignment row.""" 

35 feature_key = resolve_feature_key(assignment.get("feature_key")) 

36 assignment_type = assignment.get("type") 

37 format_value = assignment.get("format") 

38 

39 if feature_key: 

40 enabled = await feature_evaluation_service.is_enabled(feature_key, ctx) 

41 return { 

42 "feature_key": feature_key, 

43 "enabled": enabled, 

44 "locked": not enabled, 

45 "min_plan": min_plan_for_feature(feature_key), 

46 } 

47 

48 # Staff-created globals without an exam-catalog feature_key — including 

49 # STAAR/TSI/SAT/ACT + practice rows that are not tiered exam slots. 

50 return await _erudition_tests_library_meta(ctx) 

51 

52 

53async def enforce_global_assignment_access( 

54 assignment: dict[str, Any], 

55 ctx: TargetingContext, 

56) -> None: 

57 """Raise 403 when the teacher's district plan cannot access this global assignment.""" 

58 meta = await global_assignment_access_meta(assignment, ctx) 

59 if meta["locked"] or not meta["enabled"]: 

60 detail = "This global assignment is not available for your district plan." 

61 if meta.get("feature_key"): 

62 detail = ( 

63 f"Feature '{meta['feature_key']}' is not enabled for your district plan." 

64 ) 

65 raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=detail)