"""Smoke tests pinning the baseline behavior of shopdb-flask. These eight tests are the safety net required before any structural refactor proceeds. See `~/.claude/skills/pinning-flask-behavior.md`. """ import pytest def test_app_factory_creates_app(app): """create_app('testing') returns a Flask app with TESTING=True.""" assert app is not None assert app.config['TESTING'] is True assert 'sqlite' in app.config['SQLALCHEMY_DATABASE_URI'] def test_login_with_valid_credentials_returns_tokens(client, admin_user): """POST /api/auth/login with valid creds returns access and refresh tokens.""" response = client.post( '/api/auth/login', json={'username': 'testadmin', 'password': 'testpass'}, ) assert response.status_code == 200 payload = response.get_json() assert 'data' in payload data = payload['data'] assert 'access_token' in data assert 'refresh_token' in data assert 'user' in data assert data['user']['username'] == 'testadmin' def test_login_with_invalid_credentials_returns_401(client, admin_user): """Wrong password returns 401 with the documented error envelope. Pins the current shape: error info nested under `data.error` (not at top level). The error_response docstring claims top-level `error` but the implementation puts it under `data`. Pinned as-is until that inconsistency is intentionally addressed. """ response = client.post( '/api/auth/login', json={'username': 'testadmin', 'password': 'wrongpassword'}, ) assert response.status_code == 401 payload = response.get_json() assert payload['status'] == 'error' assert payload['data']['error']['code'] == 'UNAUTHORIZED' def test_login_with_missing_fields_returns_400(client): """Missing username or password returns 400 validation error.""" response = client.post('/api/auth/login', json={}) assert response.status_code == 400 def test_protected_route_requires_authentication(client, admin_user): """GET /api/users without a JWT returns 401.""" response = client.get('/api/users') assert response.status_code == 401 def test_protected_route_works_with_jwt(client, auth_headers): """GET /api/users with a valid JWT returns 200.""" response = client.get('/api/users', headers=auth_headers) assert response.status_code == 200 def test_paginated_response_shape(client, auth_headers): """A paginated list endpoint returns data plus pagination meta. Uses /api/locations because it is a simple platform endpoint that uses paginated_response. Pagination meta keys follow the naming convention (lowercase concatenated): page, perpage, total, totalpages, hasnext, hasprev. """ response = client.get('/api/locations', headers=auth_headers) assert response.status_code == 200 payload = response.get_json() assert 'data' in payload assert isinstance(payload['data'], list) assert 'meta' in payload assert 'pagination' in payload['meta'] pagination = payload['meta']['pagination'] assert 'page' in pagination assert 'perpage' in pagination assert 'total' in pagination assert 'totalpages' in pagination def test_plugin_loader_discovers_bundled_plugins(app): """Plugin manager finds the six bundled plugins.""" from shopdb.plugins import plugin_manager expected_plugins = { 'computers', 'equipment', 'network', 'notifications', 'printers', 'usb', } with app.app_context(): loader = plugin_manager.loader discovered = set(loader.discover_plugins()) assert expected_plugins.issubset(discovered), ( f'Missing bundled plugins: {expected_plugins - discovered}' )