"""Tests for admin-only skill CRUD at /api/admin/skills/. Matches the existing test_roles.py style (pytest + force_authenticate). """ import pytest from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.test import APIClient from apps.roles.models import Role, RoleSkill from apps.skills.models import Skill, UserSkill User = get_user_model() pytestmark = pytest.mark.django_db ADMIN_SKILLS_URL = '/api/admin/skills/' @pytest.fixture def admin_client(): user = User.objects.create_user( username='admin@test.example', email='admin@test.example', password='x', name='Admin', role_type='ADMIN', is_staff=True, ) c = APIClient() c.force_authenticate(user=user) return c @pytest.fixture def student_client(): user = User.objects.create_user( username='student@test.example', email='student@test.example', password='x', name='Student', role_type='STUDENT', ) c = APIClient() c.force_authenticate(user=user) return c @pytest.fixture def skill(): return Skill.objects.create( skill_name='Python', category='Programming', difficulty_level='BEGINNER', ) class TestSkillAdminPermissions: def test_student_forbidden_on_create(self, student_client): resp = student_client.post(ADMIN_SKILLS_URL, { 'skill_name': 'Rust', 'category': 'Programming', 'difficulty_level': 'INTERMEDIATE', }, format='json') assert resp.status_code == status.HTTP_403_FORBIDDEN def test_anonymous_forbidden(self, skill): resp = APIClient().get(ADMIN_SKILLS_URL) # Default DRF returns 401 for unauthenticated, 403 if auth succeeds # but permission fails. Either way, access is denied. assert resp.status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN) class TestSkillAdminCrud: def test_admin_full_cycle(self, admin_client): # Create resp = admin_client.post(ADMIN_SKILLS_URL, { 'skill_name': 'Go', 'category': 'Programming', 'description': 'Google Go', 'difficulty_level': 'INTERMEDIATE', }, format='json') assert resp.status_code == status.HTTP_201_CREATED, resp.data skill_id = resp.data['id'] # List resp = admin_client.get(ADMIN_SKILLS_URL) assert resp.status_code == status.HTTP_200_OK # Paginated response wraps rows in `results`. rows = resp.data['results'] if isinstance(resp.data, dict) else resp.data assert any(r['id'] == skill_id for r in rows) # Update resp = admin_client.patch( f'{ADMIN_SKILLS_URL}{skill_id}/', {'description': 'Updated'}, format='json', ) assert resp.status_code == status.HTTP_200_OK assert resp.data['description'] == 'Updated' # Delete (no references) succeeds resp = admin_client.delete(f'{ADMIN_SKILLS_URL}{skill_id}/') assert resp.status_code == status.HTTP_204_NO_CONTENT assert not Skill.objects.filter(pk=skill_id).exists() def test_delete_blocked_when_user_skill_references_it(self, admin_client, skill): # Reference the skill from a UserSkill row. user = User.objects.create_user( username='ref@test.example', email='ref@test.example', password='x', name='Ref', ) UserSkill.objects.create( user=user, skill=skill, proficiency=50, user_level='BEGINNER', ) resp = admin_client.delete(f'{ADMIN_SKILLS_URL}{skill.id}/') assert resp.status_code == status.HTTP_409_CONFLICT assert 'student skill profiles' in resp.data['detail'] # Skill still exists. assert Skill.objects.filter(pk=skill.id).exists() def test_delete_blocked_when_role_skill_references_it(self, admin_client, skill): role = Role.objects.create(role_name='Dev', industry='Tech') RoleSkill.objects.create( role=role, skill=skill, required_level='BEGINNER', ) resp = admin_client.delete(f'{ADMIN_SKILLS_URL}{skill.id}/') assert resp.status_code == status.HTTP_409_CONFLICT assert 'role requirements' in resp.data['detail']