90 lines
3.3 KiB
PHP
90 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
|
|
use app\common\service\DifyChatService;
|
|
|
|
$app = new think\App(dirname(__DIR__));
|
|
$app->initialize();
|
|
|
|
function assertSecretSafe(array $result, string $secret, string $message): void
|
|
{
|
|
$serialized = json_encode($result, JSON_UNESCAPED_UNICODE) ?: '';
|
|
if (str_contains($serialized, $secret)) {
|
|
fwrite(STDERR, "FAIL: {$message}\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
$qwenSecret = 'unit-test-qwen-sensitive-placeholder';
|
|
$openAiSecret = 'unit-test-openai-sensitive-placeholder';
|
|
$baseConfig = [
|
|
'enable' => false,
|
|
'base_url' => 'https://ai.example.test/v1',
|
|
'timeout' => 90,
|
|
'models' => [
|
|
'qwen' => ['name' => 'qwen-test', 'label' => 'Qwen', 'api_key' => $qwenSecret],
|
|
'openai' => ['name' => 'openai-test', 'label' => 'OpenAI', 'api_key' => $openAiSecret],
|
|
],
|
|
];
|
|
|
|
function assertAllSecretsSafe(array $result, array $secrets, string $message): void
|
|
{
|
|
foreach ($secrets as $secret) {
|
|
assertSecretSafe($result, $secret, $message);
|
|
}
|
|
}
|
|
|
|
$secrets = [$qwenSecret, $openAiSecret];
|
|
|
|
$resolveProfile = (new ReflectionClass(DifyChatService::class))->getMethod('resolveProfileConfig');
|
|
$resolvedQwen = $resolveProfile->invoke(null, $baseConfig, 'qwen');
|
|
$resolvedOpenAi = $resolveProfile->invoke(null, $baseConfig, 'openai');
|
|
if (
|
|
!is_array($resolvedQwen)
|
|
|| !is_array($resolvedOpenAi)
|
|
|| ($resolvedQwen['api_key'] ?? null) !== $qwenSecret
|
|
|| ($resolvedOpenAi['api_key'] ?? null) !== $openAiSecret
|
|
) {
|
|
fwrite(STDERR, "FAIL: each model key must resolve only its own server credential\n");
|
|
exit(1);
|
|
}
|
|
if ($resolveProfile->invoke(null, $baseConfig, 'other') !== null) {
|
|
fwrite(STDERR, "FAIL: non-whitelisted profile must not resolve server configuration\n");
|
|
exit(1);
|
|
}
|
|
|
|
config($baseConfig, 'prescription_ai');
|
|
$disabled = DifyChatService::chat('qwen', [], 'test', 'test-user');
|
|
assertAllSecretsSafe($disabled, $secrets, 'disabled response must not expose credentials');
|
|
|
|
$enabledConfig = $baseConfig;
|
|
$enabledConfig['enable'] = true;
|
|
config($enabledConfig, 'prescription_ai');
|
|
foreach (['other', 'QWEN', ' openai', 'gpt-5.6-sol'] as $invalidProfile) {
|
|
$invalid = DifyChatService::chat($invalidProfile, [], 'test', 'test-user');
|
|
if (($invalid['error_code'] ?? '') !== 'INVALID_PROFILE') {
|
|
fwrite(STDERR, "FAIL: invalid profile must be rejected before upstream work\n");
|
|
exit(1);
|
|
}
|
|
assertAllSecretsSafe($invalid, $secrets, 'invalid-profile response must not expose credentials');
|
|
}
|
|
|
|
$invalidUrlConfig = $baseConfig;
|
|
$invalidUrlConfig['enable'] = true;
|
|
$invalidUrlConfig['base_url'] = 'file:///not-allowed';
|
|
config($invalidUrlConfig, 'prescription_ai');
|
|
$invalidUrl = DifyChatService::chat('qwen', [], 'test', 'test-user');
|
|
assertAllSecretsSafe($invalidUrl, $secrets, 'invalid URL response must not expose credentials');
|
|
|
|
$headerInjectionConfig = $baseConfig;
|
|
$headerInjectionConfig['enable'] = true;
|
|
$headerInjectionConfig['models']['qwen']['api_key'] = $qwenSecret . "\r\nInjected: value";
|
|
config($headerInjectionConfig, 'prescription_ai');
|
|
$headerInjection = DifyChatService::chat('qwen', [], 'test', 'test-user');
|
|
assertAllSecretsSafe($headerInjection, $secrets, 'invalid credential response must not expose credentials');
|
|
|
|
echo "Prescription AI secret safety: OK\n";
|