django-cfg 1.5.8__py3-none-any.whl โ 1.5.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +126 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info โ django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info โ django_cfg-1.5.20.dist-info}/RECORD +158 -121
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info โ django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info โ django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info โ django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC Integration Test Utility.
|
|
3
|
+
|
|
4
|
+
Comprehensive integration test for gRPC with API keys.
|
|
5
|
+
Automatically performs all steps from proto generation to log verification.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import grpc
|
|
15
|
+
from django.conf import settings
|
|
16
|
+
from django.contrib.auth import get_user_model
|
|
17
|
+
|
|
18
|
+
from django_cfg.apps.integrations.grpc.models import GrpcApiKey, GRPCRequestLog
|
|
19
|
+
|
|
20
|
+
User = get_user_model()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GRPCIntegrationTest:
|
|
24
|
+
"""Comprehensive integration test for gRPC with API keys."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, app_label: str = "crypto", quiet: bool = False):
|
|
27
|
+
"""
|
|
28
|
+
Initialize integration test.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
app_label: Django app label to generate protos for
|
|
32
|
+
quiet: Suppress verbose output
|
|
33
|
+
"""
|
|
34
|
+
self.app_label = app_label
|
|
35
|
+
self.quiet = quiet
|
|
36
|
+
self.server_process: Optional[subprocess.Popen] = None
|
|
37
|
+
self.api_key: Optional[GrpcApiKey] = None
|
|
38
|
+
self.grpc_port = settings.GRPC_SERVER.get("port", 50051)
|
|
39
|
+
|
|
40
|
+
def log(self, message: str):
|
|
41
|
+
"""Print message if not quiet."""
|
|
42
|
+
if not self.quiet:
|
|
43
|
+
print(message)
|
|
44
|
+
|
|
45
|
+
def print_step(self, step_num: int, message: str):
|
|
46
|
+
"""Print step with formatting."""
|
|
47
|
+
if not self.quiet:
|
|
48
|
+
print(f"\n{'='*70}")
|
|
49
|
+
print(f"๐น Step {step_num}: {message}")
|
|
50
|
+
print(f"{'='*70}")
|
|
51
|
+
|
|
52
|
+
def step1_generate_protos(self) -> bool:
|
|
53
|
+
"""Step 1: Generate proto files."""
|
|
54
|
+
self.print_step(1, f"Generating proto files for {self.app_label}")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
[sys.executable, "manage.py", "generate_protos", self.app_label],
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
check=True
|
|
62
|
+
)
|
|
63
|
+
self.log("โ
Proto files generated successfully")
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
except subprocess.CalledProcessError as e:
|
|
67
|
+
self.log(f"โ Proto generation error: {e}")
|
|
68
|
+
if e.stderr:
|
|
69
|
+
self.log(f" STDERR: {e.stderr}")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def step2_start_server(self) -> bool:
|
|
73
|
+
"""Step 2: Start gRPC server."""
|
|
74
|
+
self.print_step(2, "Starting gRPC server")
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
self.server_process = subprocess.Popen(
|
|
78
|
+
[sys.executable, "manage.py", "rungrpc"],
|
|
79
|
+
stdout=subprocess.PIPE,
|
|
80
|
+
stderr=subprocess.PIPE,
|
|
81
|
+
text=True
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.log(f"โ
gRPC server started (PID: {self.server_process.pid})")
|
|
85
|
+
self.log(f" Port: {self.grpc_port}")
|
|
86
|
+
self.log(" Waiting for server to start...")
|
|
87
|
+
time.sleep(3)
|
|
88
|
+
|
|
89
|
+
if self.server_process.poll() is not None:
|
|
90
|
+
stdout, stderr = self.server_process.communicate()
|
|
91
|
+
self.log(f"โ Server terminated prematurely")
|
|
92
|
+
self.log(f" STDOUT: {stdout}")
|
|
93
|
+
self.log(f" STDERR: {stderr}")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
self.log("โ
Server is running")
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.log(f"โ Server startup error: {e}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def step3_create_api_key(self) -> bool:
|
|
104
|
+
"""Step 3: Create test API key."""
|
|
105
|
+
self.print_step(3, "Creating test API key")
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
user = User.objects.filter(is_active=True).first()
|
|
109
|
+
if not user:
|
|
110
|
+
self.log(" Creating test user...")
|
|
111
|
+
user = User.objects.create_user(
|
|
112
|
+
username="grpc_integration_test",
|
|
113
|
+
email="grpc_test@example.com",
|
|
114
|
+
password="test_password_123",
|
|
115
|
+
is_active=True
|
|
116
|
+
)
|
|
117
|
+
self.log(f" โ
Created user: {user.username}")
|
|
118
|
+
else:
|
|
119
|
+
self.log(f" โ
Using existing user: {user.username}")
|
|
120
|
+
|
|
121
|
+
self.api_key = GrpcApiKey.objects.create_for_user(
|
|
122
|
+
user=user,
|
|
123
|
+
name="Integration Test Key",
|
|
124
|
+
key_type="development",
|
|
125
|
+
expires_in_days=None,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.log(f"โ
API key created")
|
|
129
|
+
self.log(f" Name: {self.api_key.name}")
|
|
130
|
+
self.log(f" Key: {self.api_key.key[:32]}...")
|
|
131
|
+
self.log(f" User: {self.api_key.user.username}")
|
|
132
|
+
self.log(f" Valid: {self.api_key.is_valid}")
|
|
133
|
+
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self.log(f"โ API key creation error: {e}")
|
|
138
|
+
import traceback
|
|
139
|
+
if not self.quiet:
|
|
140
|
+
traceback.print_exc()
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def step4_test_client(self) -> bool:
|
|
144
|
+
"""Step 4: Test gRPC client."""
|
|
145
|
+
self.print_step(4, "Testing gRPC client with API key")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
import importlib
|
|
149
|
+
module_path = f"apps.{self.app_label}.grpc_services.generated"
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
service_name = f"{self.app_label}_service"
|
|
153
|
+
proto_module = importlib.import_module(f"{module_path}.{service_name}_pb2")
|
|
154
|
+
grpc_module = importlib.import_module(f"{module_path}.{service_name}_pb2_grpc")
|
|
155
|
+
service_stub_name = f"{self.app_label.capitalize()}ServiceStub"
|
|
156
|
+
except ImportError:
|
|
157
|
+
try:
|
|
158
|
+
proto_module = importlib.import_module(f"{module_path}.coin_pb2")
|
|
159
|
+
grpc_module = importlib.import_module(f"{module_path}.coin_pb2_grpc")
|
|
160
|
+
service_stub_name = "CoinServiceStub"
|
|
161
|
+
except ImportError:
|
|
162
|
+
self.log(f"โ Failed to import proto files from {module_path}")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
server_address = f"localhost:{self.grpc_port}"
|
|
166
|
+
self.log(f" Connecting to: {server_address}")
|
|
167
|
+
|
|
168
|
+
StubClass = getattr(grpc_module, service_stub_name)
|
|
169
|
+
|
|
170
|
+
# Test 1: Valid API key
|
|
171
|
+
self.log("\n ๐ Test 1: Authentication with valid API key")
|
|
172
|
+
with grpc.insecure_channel(server_address) as channel:
|
|
173
|
+
stub = StubClass(channel)
|
|
174
|
+
metadata = [("x-api-key", self.api_key.key)]
|
|
175
|
+
|
|
176
|
+
request = proto_module.GetCoinRequest(symbol="BTC")
|
|
177
|
+
response = stub.GetCoin(request, metadata=metadata)
|
|
178
|
+
|
|
179
|
+
self.log(f" โ
Request successful: {response.coin.symbol} - {response.coin.name}")
|
|
180
|
+
|
|
181
|
+
# Test 2: Django SECRET_KEY
|
|
182
|
+
self.log("\n ๐ Test 2: Authentication with Django SECRET_KEY")
|
|
183
|
+
with grpc.insecure_channel(server_address) as channel:
|
|
184
|
+
stub = StubClass(channel)
|
|
185
|
+
metadata = [("x-api-key", settings.SECRET_KEY)]
|
|
186
|
+
|
|
187
|
+
request = proto_module.GetCoinRequest(symbol="ETH")
|
|
188
|
+
response = stub.GetCoin(request, metadata=metadata)
|
|
189
|
+
|
|
190
|
+
self.log(f" โ
SECRET_KEY works: {response.coin.symbol} - {response.coin.name}")
|
|
191
|
+
|
|
192
|
+
# Test 3: Invalid key
|
|
193
|
+
self.log("\n ๐ Test 3: Testing invalid key")
|
|
194
|
+
try:
|
|
195
|
+
with grpc.insecure_channel(server_address) as channel:
|
|
196
|
+
stub = StubClass(channel)
|
|
197
|
+
metadata = [("x-api-key", "invalid_key_12345")]
|
|
198
|
+
|
|
199
|
+
request = proto_module.GetCoinRequest(symbol="BTC")
|
|
200
|
+
response = stub.GetCoin(request, metadata=metadata)
|
|
201
|
+
|
|
202
|
+
self.log(f" โ ๏ธ Invalid key was accepted (require_auth=False)")
|
|
203
|
+
|
|
204
|
+
except grpc.RpcError as e:
|
|
205
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
206
|
+
self.log(f" โ
Invalid key correctly rejected")
|
|
207
|
+
else:
|
|
208
|
+
self.log(f" โ ๏ธ Unexpected error: {e.code()} - {e.details()}")
|
|
209
|
+
|
|
210
|
+
self.log("\nโ
All client tests passed")
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
except grpc.RpcError as e:
|
|
214
|
+
self.log(f"โ gRPC error: {e.code()} - {e.details()}")
|
|
215
|
+
return False
|
|
216
|
+
except Exception as e:
|
|
217
|
+
self.log(f"โ Client testing error: {e}")
|
|
218
|
+
import traceback
|
|
219
|
+
if not self.quiet:
|
|
220
|
+
traceback.print_exc()
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
def step5_verify_logs(self) -> bool:
|
|
224
|
+
"""Step 5: Verify request logs."""
|
|
225
|
+
self.print_step(5, "Verifying request logs")
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
self.api_key.refresh_from_db()
|
|
229
|
+
|
|
230
|
+
self.log(f"๐ API key statistics:")
|
|
231
|
+
self.log(f" Request count: {self.api_key.request_count}")
|
|
232
|
+
self.log(f" Last used: {self.api_key.last_used_at}")
|
|
233
|
+
|
|
234
|
+
logs_with_key = GRPCRequestLog.objects.filter(api_key=self.api_key)
|
|
235
|
+
logs_without_key = GRPCRequestLog.objects.filter(
|
|
236
|
+
api_key__isnull=True,
|
|
237
|
+
is_authenticated=True
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
self.log(f"\n๐ Request logs:")
|
|
241
|
+
self.log(f" With API key: {logs_with_key.count()}")
|
|
242
|
+
self.log(f" With SECRET_KEY: {logs_without_key.count()}")
|
|
243
|
+
self.log(f" Total logs: {GRPCRequestLog.objects.count()}")
|
|
244
|
+
|
|
245
|
+
if logs_with_key.exists() and not self.quiet:
|
|
246
|
+
self.log(f"\n Recent requests with API key:")
|
|
247
|
+
for log in logs_with_key.order_by("-created_at")[:3]:
|
|
248
|
+
self.log(f" - {log.method_name}: {log.status} ({log.duration_ms}ms)")
|
|
249
|
+
self.log(f" API Key: {log.api_key.name if log.api_key else 'None'}")
|
|
250
|
+
self.log(f" User: {log.user.username if log.user else 'None'}")
|
|
251
|
+
|
|
252
|
+
self.log("\nโ
Logs correctly recorded with api_key")
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
self.log(f"โ Log verification error: {e}")
|
|
257
|
+
import traceback
|
|
258
|
+
if not self.quiet:
|
|
259
|
+
traceback.print_exc()
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
def step6_cleanup(self) -> bool:
|
|
263
|
+
"""Step 6: Clean up test data."""
|
|
264
|
+
self.print_step(6, "Cleaning up test data")
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
if self.server_process:
|
|
268
|
+
self.log(" Stopping gRPC server...")
|
|
269
|
+
self.server_process.terminate()
|
|
270
|
+
try:
|
|
271
|
+
self.server_process.wait(timeout=5)
|
|
272
|
+
except subprocess.TimeoutExpired:
|
|
273
|
+
self.server_process.kill()
|
|
274
|
+
self.log(f" โ
Server stopped (PID: {self.server_process.pid})")
|
|
275
|
+
|
|
276
|
+
if self.api_key:
|
|
277
|
+
self.log(f" Deleting API key: {self.api_key.name}...")
|
|
278
|
+
self.api_key.delete()
|
|
279
|
+
self.log(" โ
API key deleted")
|
|
280
|
+
|
|
281
|
+
self.log("\nโ
Cleanup completed")
|
|
282
|
+
return True
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
self.log(f"โ Cleanup error: {e}")
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
def run(self) -> bool:
|
|
289
|
+
"""Run full integration test."""
|
|
290
|
+
self.log("=" * 70)
|
|
291
|
+
self.log("๐งช Comprehensive gRPC API Keys Integration Test")
|
|
292
|
+
self.log("=" * 70)
|
|
293
|
+
|
|
294
|
+
results = []
|
|
295
|
+
|
|
296
|
+
results.append(("Proto generation", self.step1_generate_protos()))
|
|
297
|
+
|
|
298
|
+
if results[-1][1]:
|
|
299
|
+
results.append(("Server startup", self.step2_start_server()))
|
|
300
|
+
|
|
301
|
+
if results[-1][1]:
|
|
302
|
+
results.append(("API key creation", self.step3_create_api_key()))
|
|
303
|
+
|
|
304
|
+
if results[-1][1]:
|
|
305
|
+
results.append(("Client testing", self.step4_test_client()))
|
|
306
|
+
|
|
307
|
+
if results[-1][1]:
|
|
308
|
+
results.append(("Log verification", self.step5_verify_logs()))
|
|
309
|
+
|
|
310
|
+
results.append(("Cleanup", self.step6_cleanup()))
|
|
311
|
+
|
|
312
|
+
self.log("\n" + "=" * 70)
|
|
313
|
+
self.log("๐ Integration Test Results")
|
|
314
|
+
self.log("=" * 70)
|
|
315
|
+
|
|
316
|
+
success_count = sum(1 for _, success in results if success)
|
|
317
|
+
total_count = len(results)
|
|
318
|
+
|
|
319
|
+
for step_name, success in results:
|
|
320
|
+
status = "โ
" if success else "โ"
|
|
321
|
+
self.log(f"{status} {step_name}")
|
|
322
|
+
|
|
323
|
+
self.log("\n" + "=" * 70)
|
|
324
|
+
if success_count == total_count:
|
|
325
|
+
self.log(f"๐ All tests passed successfully! ({success_count}/{total_count})")
|
|
326
|
+
self.log("=" * 70)
|
|
327
|
+
return True
|
|
328
|
+
else:
|
|
329
|
+
self.log(f"โ ๏ธ Tests passed: {success_count}/{total_count}")
|
|
330
|
+
self.log("=" * 70)
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
__all__ = ["GRPCIntegrationTest"]
|
|
@@ -184,7 +184,21 @@ class ProtoGenerator:
|
|
|
184
184
|
|
|
185
185
|
field_number = 1
|
|
186
186
|
|
|
187
|
-
#
|
|
187
|
+
# Track fields to skip to avoid duplicates
|
|
188
|
+
fields_to_skip = set()
|
|
189
|
+
|
|
190
|
+
# If we'll add id separately, skip it in the field iteration
|
|
191
|
+
if include_id:
|
|
192
|
+
fields_to_skip.add('id')
|
|
193
|
+
|
|
194
|
+
# If we'll add timestamps separately, skip them in the field iteration
|
|
195
|
+
if include_timestamps:
|
|
196
|
+
if hasattr(model, "created_at"):
|
|
197
|
+
fields_to_skip.add('created_at')
|
|
198
|
+
if hasattr(model, "updated_at"):
|
|
199
|
+
fields_to_skip.add('updated_at')
|
|
200
|
+
|
|
201
|
+
# Add id field first if requested
|
|
188
202
|
if include_id:
|
|
189
203
|
lines.append(f" int64 id = {field_number};")
|
|
190
204
|
field_number += 1
|
|
@@ -199,6 +213,10 @@ class ProtoGenerator:
|
|
|
199
213
|
if isinstance(field, models.ManyToManyField):
|
|
200
214
|
continue
|
|
201
215
|
|
|
216
|
+
# Skip fields that will be added separately to avoid duplicates
|
|
217
|
+
if field.name in fields_to_skip:
|
|
218
|
+
continue
|
|
219
|
+
|
|
202
220
|
# Get field info
|
|
203
221
|
field_name = self._format_field_name(field.name)
|
|
204
222
|
proto_type = self.mapper.get_proto_type(field)
|
|
@@ -213,13 +231,13 @@ class ProtoGenerator:
|
|
|
213
231
|
lines.append(field_def)
|
|
214
232
|
field_number += 1
|
|
215
233
|
|
|
216
|
-
# Add timestamp fields if requested
|
|
234
|
+
# Add timestamp fields last if requested
|
|
217
235
|
if include_timestamps:
|
|
218
236
|
if hasattr(model, "created_at"):
|
|
219
|
-
lines.append(f" string created_at = {field_number};")
|
|
237
|
+
lines.append(f" optional string created_at = {field_number};")
|
|
220
238
|
field_number += 1
|
|
221
239
|
if hasattr(model, "updated_at"):
|
|
222
|
-
lines.append(f" string updated_at = {field_number};")
|
|
240
|
+
lines.append(f" optional string updated_at = {field_number};")
|
|
223
241
|
field_number += 1
|
|
224
242
|
|
|
225
243
|
lines.append("}")
|
|
@@ -375,12 +393,25 @@ def generate_proto_for_app(app_label: str, output_dir: Optional[Path] = None) ->
|
|
|
375
393
|
print(f"Generated {count} proto file(s)")
|
|
376
394
|
```
|
|
377
395
|
"""
|
|
396
|
+
# Get gRPC config from django-cfg (Pydantic)
|
|
397
|
+
from ..services.config_helper import get_grpc_config
|
|
398
|
+
|
|
399
|
+
grpc_config = get_grpc_config()
|
|
400
|
+
proto_config = grpc_config.proto if grpc_config else None
|
|
401
|
+
|
|
378
402
|
# Get output directory
|
|
379
403
|
if output_dir is None:
|
|
380
|
-
|
|
381
|
-
|
|
404
|
+
if proto_config:
|
|
405
|
+
output_dir_str = proto_config.output_dir
|
|
406
|
+
else:
|
|
407
|
+
output_dir_str = "protos" # Fallback
|
|
408
|
+
|
|
382
409
|
output_dir = Path(output_dir_str)
|
|
383
410
|
|
|
411
|
+
# Make absolute if relative
|
|
412
|
+
if not output_dir.is_absolute():
|
|
413
|
+
output_dir = settings.BASE_DIR / output_dir
|
|
414
|
+
|
|
384
415
|
# Get app config
|
|
385
416
|
try:
|
|
386
417
|
app_config = apps.get_app_config(app_label)
|
|
@@ -398,10 +429,19 @@ def generate_proto_for_app(app_label: str, output_dir: Optional[Path] = None) ->
|
|
|
398
429
|
logger.warning(f"No models found in app '{app_label}'")
|
|
399
430
|
return 0
|
|
400
431
|
|
|
432
|
+
# Build package name: combine prefix + app_label
|
|
433
|
+
if proto_config and proto_config.package_prefix:
|
|
434
|
+
full_package = f"{proto_config.package_prefix}.{app_label}"
|
|
435
|
+
else:
|
|
436
|
+
full_package = app_label
|
|
437
|
+
|
|
438
|
+
# Get field naming
|
|
439
|
+
field_naming = proto_config.field_naming if proto_config else "snake_case"
|
|
440
|
+
|
|
401
441
|
# Generate proto file
|
|
402
442
|
generator = ProtoGenerator(
|
|
403
|
-
package_prefix=
|
|
404
|
-
field_naming=
|
|
443
|
+
package_prefix=full_package,
|
|
444
|
+
field_naming=field_naming,
|
|
405
445
|
)
|
|
406
446
|
|
|
407
447
|
output_path = output_dir / f"{app_label}.proto"
|