test_openapi_schema.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. """
  2. Unit tests for OpenAPI schema generation.
  3. Refs: #20638
  4. """
  5. import json
  6. from django.test import TestCase
  7. class OpenAPISchemaTestCase(TestCase):
  8. """Tests for OpenAPI schema generation."""
  9. def setUp(self):
  10. """Fetch schema via API endpoint."""
  11. response = self.client.get('/api/schema/', {'format': 'json'})
  12. self.assertEqual(response.status_code, 200)
  13. self.schema = json.loads(response.content)
  14. def test_post_operation_documents_single_or_array(self):
  15. """
  16. POST operations on NetBoxModelViewSet endpoints should document
  17. support for both single objects and arrays via oneOf.
  18. Refs: #20638
  19. """
  20. # Test representative endpoints across different apps
  21. test_paths = [
  22. '/api/core/data-sources/',
  23. '/api/dcim/sites/',
  24. '/api/users/users/',
  25. '/api/ipam/ip-addresses/',
  26. ]
  27. for path in test_paths:
  28. with self.subTest(path=path):
  29. operation = self.schema['paths'][path]['post']
  30. # Get the request body schema
  31. request_schema = operation['requestBody']['content']['application/json']['schema']
  32. # Should have oneOf with two options
  33. self.assertIn('oneOf', request_schema, f"POST {path} should have oneOf schema")
  34. self.assertEqual(
  35. len(request_schema['oneOf']), 2,
  36. f"POST {path} oneOf should have exactly 2 options"
  37. )
  38. # First option: single object (has $ref or properties)
  39. single_schema = request_schema['oneOf'][0]
  40. self.assertTrue(
  41. '$ref' in single_schema or 'properties' in single_schema,
  42. f"POST {path} first oneOf option should be single object"
  43. )
  44. # Second option: array of objects
  45. array_schema = request_schema['oneOf'][1]
  46. self.assertEqual(
  47. array_schema['type'], 'array',
  48. f"POST {path} second oneOf option should be array"
  49. )
  50. self.assertIn('items', array_schema, f"POST {path} array should have items")
  51. def test_bulk_update_operations_require_array_only(self):
  52. """
  53. Bulk update/patch operations should require arrays only, not oneOf.
  54. They don't support single object input.
  55. Refs: #20638
  56. """
  57. test_paths = [
  58. '/api/dcim/sites/',
  59. '/api/users/users/',
  60. ]
  61. for path in test_paths:
  62. for method in ['put', 'patch']:
  63. with self.subTest(path=path, method=method):
  64. operation = self.schema['paths'][path][method]
  65. request_schema = operation['requestBody']['content']['application/json']['schema']
  66. # Should be array-only, not oneOf
  67. self.assertNotIn(
  68. 'oneOf', request_schema,
  69. f"{method.upper()} {path} should NOT have oneOf (array-only)"
  70. )
  71. self.assertEqual(
  72. request_schema['type'], 'array',
  73. f"{method.upper()} {path} should require array"
  74. )
  75. self.assertIn(
  76. 'items', request_schema,
  77. f"{method.upper()} {path} array should have items"
  78. )
  79. def test_bulk_delete_requires_array(self):
  80. """
  81. Bulk delete operations should require arrays.
  82. Refs: #20638
  83. """
  84. path = '/api/dcim/sites/'
  85. operation = self.schema['paths'][path]['delete']
  86. request_schema = operation['requestBody']['content']['application/json']['schema']
  87. # Should be array-only
  88. self.assertNotIn('oneOf', request_schema, "DELETE should NOT have oneOf")
  89. self.assertEqual(request_schema['type'], 'array', "DELETE should require array")
  90. self.assertIn('items', request_schema, "DELETE array should have items")