fix: stabilize post-MVP travel-agent and itinerary workflows
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from django.db import IntegrityError
|
||||
|
||||
from .models import (
|
||||
EncryptionConfigurationError,
|
||||
ImmichIntegration,
|
||||
@@ -41,12 +43,28 @@ class UserAPIKeySerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
api_key = validated_data.pop("api_key")
|
||||
user = self.context["request"].user
|
||||
instance = UserAPIKey(user=user, **validated_data)
|
||||
|
||||
provider = validated_data.get("provider")
|
||||
|
||||
try:
|
||||
instance, _ = UserAPIKey.objects.get_or_create(
|
||||
user=user,
|
||||
provider=provider,
|
||||
defaults={"encrypted_api_key": ""},
|
||||
)
|
||||
instance.set_api_key(api_key)
|
||||
except EncryptionConfigurationError as exc:
|
||||
raise serializers.ValidationError({"api_key": str(exc)}) from exc
|
||||
instance.save()
|
||||
except IntegrityError:
|
||||
# Defensive retry: in highly concurrent requests a competing create can
|
||||
# still race. Fall back to updating the existing row instead of 500.
|
||||
instance = UserAPIKey.objects.get(user=user, provider=provider)
|
||||
try:
|
||||
instance.set_api_key(api_key)
|
||||
except EncryptionConfigurationError as exc:
|
||||
raise serializers.ValidationError({"api_key": str(exc)}) from exc
|
||||
|
||||
instance.save(update_fields=["encrypted_api_key", "updated_at"])
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
||||
@@ -51,3 +51,65 @@ class UserAPIKeyConfigurationTests(APITestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn("not configured", response.json().get("error", "").lower())
|
||||
mock_requests_get.assert_not_called()
|
||||
|
||||
|
||||
class UserAPIKeyCreateBehaviorTests(APITestCase):
|
||||
@override_settings(
|
||||
FIELD_ENCRYPTION_KEY="YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
|
||||
)
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="api-key-create-user",
|
||||
email="apikey-create@example.com",
|
||||
password="password123",
|
||||
)
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
@override_settings(
|
||||
FIELD_ENCRYPTION_KEY="YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
|
||||
)
|
||||
def test_duplicate_provider_post_updates_existing_key(self):
|
||||
first_response = self.client.post(
|
||||
"/api/integrations/api-keys/",
|
||||
{"provider": "google_maps", "api_key": "first-secret"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(first_response.status_code, 201)
|
||||
|
||||
second_response = self.client.post(
|
||||
"/api/integrations/api-keys/",
|
||||
{"provider": "google_maps", "api_key": "second-secret"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(second_response.status_code, 201)
|
||||
|
||||
from integrations.models import UserAPIKey
|
||||
|
||||
records = UserAPIKey.objects.filter(user=self.user, provider="google_maps")
|
||||
self.assertEqual(records.count(), 1)
|
||||
self.assertEqual(records.first().get_api_key(), "second-secret")
|
||||
|
||||
@override_settings(
|
||||
FIELD_ENCRYPTION_KEY="YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
|
||||
)
|
||||
def test_provider_is_normalized_and_still_upserts(self):
|
||||
self.client.post(
|
||||
"/api/integrations/api-keys/",
|
||||
{"provider": "Google_Maps", "api_key": "first-secret"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
"/api/integrations/api-keys/",
|
||||
{"provider": " google_maps ", "api_key": "rotated-secret"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
from integrations.models import UserAPIKey
|
||||
|
||||
records = UserAPIKey.objects.filter(user=self.user, provider="google_maps")
|
||||
self.assertEqual(records.count(), 1)
|
||||
self.assertEqual(records.first().get_api_key(), "rotated-secret")
|
||||
|
||||
Reference in New Issue
Block a user