gapguide-api / tests /test_recommendations_cross_module.py
arifRB's picture
Deploy GapGuide backend (Docker)
ffd36e0 verified
Raw
History Blame Contribute Delete
5.17 kB
"""Cross-module smoke test: seed skills β†’ seed roles β†’ seed resources β†’
gap report keys match recommendation keys β†’ recommended IDs resolve through
/api/resources/<id>/.
Different from the unit tests: this exercises the full seed chain against
the real curated YAML files, not hand-crafted fixtures.
"""
from io import StringIO
import pytest
from django.core.management import call_command
from rest_framework.test import APIClient
from apps.resources.models import Resource
from apps.roles.models import Role, UserTargetRole
from apps.skills.models import Skill, UserSkill
pytestmark = pytest.mark.django_db
REGISTER = '/api/auth/register/'
USER_SKILLS = '/api/user-skills/'
TARGET_ROLE = '/api/target-role/'
ANALYSIS = '/api/analysis/'
RECOMMENDATIONS = '/api/recommendations/'
RESOURCES = '/api/resources/'
@pytest.fixture
def seeded(db):
"""Full curated seed β€” skills, roles, resources."""
call_command('seed_initial_skills', stdout=StringIO())
call_command('seed_initial_roles', stdout=StringIO())
call_command('seed_initial_resources', stdout=StringIO())
return True
def _auth_client():
c = APIClient()
r = c.post(REGISTER, data={
'name': 'Cross', 'email': 'cross@x.com',
'password': 'StrongPass123!', 'password_confirm': 'StrongPass123!',
}, format='json')
assert r.status_code == 201, r.data
c.credentials(HTTP_AUTHORIZATION=f'Bearer {r.data["access"]}')
return c
def test_gap_skills_match_recommendation_skills(seeded):
"""Every gap skill (MISSING/INSUFFICIENT) in the report appears as a key
in the recommendations payload. MET skills must NOT appear."""
c = _auth_client()
role = Role.objects.get(role_name='Data Scientist')
c.post(TARGET_ROLE, data={'role_id': role.id}, format='json')
# Give the user partial skills so some gaps exist, some are MET.
python_id = Skill.objects.get(skill_name='Python').id
c.post(USER_SKILLS, data={'skill_id': python_id, 'proficiency': 100},
format='json')
gap_report = c.get(ANALYSIS).data
rec_payload = c.get(RECOMMENDATIONS).data
gap_skill_ids = {g['skill_id'] for g in gap_report['gaps']
if g['gap_type'] != 'MET'}
met_skill_ids = {g['skill_id'] for g in gap_report['gaps']
if g['gap_type'] == 'MET'}
rec_skill_ids = {int(k) for k in rec_payload['recommendations']}
assert gap_skill_ids == rec_skill_ids
assert rec_skill_ids.isdisjoint(met_skill_ids)
def test_recommended_resource_ids_match_resources_endpoint(seeded):
c = _auth_client()
role = Role.objects.get(role_name='Data Scientist')
c.post(TARGET_ROLE, data={'role_id': role.id}, format='json')
rec_payload = c.get(RECOMMENDATIONS).data
for skill_id, items in rec_payload['recommendations'].items():
for item in items:
r = c.get(f'{RESOURCES}{item["resource_id"]}/')
assert r.status_code == 200
assert r.data['id'] == item['resource_id']
assert r.data['url'] == item['url']
def test_mandatory_gap_gets_recommendations(seeded):
"""Python is mandatory for Data Scientist; with 0 proficiency, the
recommendation payload MUST contain β‰₯1 Python-linked resource."""
c = _auth_client()
role = Role.objects.get(role_name='Data Scientist')
c.post(TARGET_ROLE, data={'role_id': role.id}, format='json')
python_id = Skill.objects.get(skill_name='Python').id
rec_payload = c.get(RECOMMENDATIONS).data
python_recs = rec_payload['recommendations'].get(str(python_id)) \
or rec_payload['recommendations'].get(python_id)
assert python_recs, 'Python gap should get β‰₯1 recommendation from curated catalog'
def test_severity_does_not_filter_recommendations(seeded):
"""Low / Medium / High severity all deserve recommendations β€”
rank, don't silently drop."""
c = _auth_client()
role = Role.objects.get(role_name='Data Scientist')
c.post(TARGET_ROLE, data={'role_id': role.id}, format='json')
# Boost a skill just below threshold β†’ LOW/MEDIUM severity, not 0.
python_id = Skill.objects.get(skill_name='Python').id
c.post(USER_SKILLS, data={'skill_id': python_id, 'proficiency': 55},
format='json')
report = c.get(ANALYSIS).data
python_gap = next(g for g in report['gaps'] if g['skill_id'] == python_id)
assert python_gap['gap_type'] == 'INSUFFICIENT' # just below 60 threshold
rec = c.get(RECOMMENDATIONS).data
py_recs = rec['recommendations'].get(str(python_id)) \
or rec['recommendations'].get(python_id)
assert py_recs, 'low-severity insufficient gap should still surface recs'
def test_role_param_routes_to_different_role(seeded):
"""?role=<id> overrides the active target role."""
c = _auth_client()
ds = Role.objects.get(role_name='Data Scientist')
fe = Role.objects.get(role_name='Frontend Developer')
c.post(TARGET_ROLE, data={'role_id': ds.id}, format='json')
r = c.get(f'{RECOMMENDATIONS}?role={fe.id}')
assert r.status_code == 200
assert r.data['role_id'] == fe.id
assert r.data['role_name'] == 'Frontend Developer'