bootstrap.php•4.74 kB
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../src/Helpers.php';
// Ensure clean environment at bootstrap
use OpenFGA\{Client, ClientInterface};
// Wait for OpenFGA to be ready
function waitForOpenFGA(string $url, int $maxAttempts = 60): void
{
$attempt = 0;
while ($attempt < $maxAttempts) {
try {
$ch = curl_init($url . '/healthz');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if (200 === $httpCode) {
echo "OpenFGA is ready!\n";
// Give it a moment to fully initialize
sleep(2);
return;
}
if ($error) {
echo "Connection error: {$error}\n";
} else {
echo "Health check returned HTTP {$httpCode}\n";
}
} catch (Exception $e) {
echo 'Exception during health check: ' . $e->getMessage() . "\n";
}
$attempt++;
echo "Waiting for OpenFGA... (attempt {$attempt}/{$maxAttempts})\n";
sleep(2);
}
throw new RuntimeException('OpenFGA failed to start within ' . ($maxAttempts * 2) . ' seconds');
}
// Get OpenFGA URL from environment
$openfgaUrl = getenv('OPENFGA_MCP_API_URL') ?: 'http://localhost:8080';
// Wait for OpenFGA to be ready
waitForOpenFGA($openfgaUrl);
// Create a shared OpenFGA client for tests
$GLOBALS['openfga_client'] = new Client(url: $openfgaUrl);
// Helper function to get the test client
function getTestClient(): ClientInterface
{
return $GLOBALS['openfga_client'];
}
// Helper function to create a test store
function createTestStore(?string $name = null): string
{
$name ??= 'test-store-' . uniqid();
$client = getTestClient();
$storeId = null;
$client->createStore($name)
->success(function ($response) use (&$storeId): void {
$storeId = $response->getId();
})
->failure(function ($error): void {
throw new RuntimeException('Failed to create test store: ' . $error->getMessage());
});
if (! $storeId) {
throw new RuntimeException('Failed to create test store');
}
return $storeId;
}
// Helper function to delete a test store
function deleteTestStore(string $storeId): void
{
$client = getTestClient();
$client->deleteStore($storeId)
->failure(function ($error): void {
// Ignore deletion errors in cleanup
echo 'Warning: Failed to delete test store: ' . $error->getMessage() . "\n";
});
}
// Helper function to create a test model
function createTestModel(string $storeId, ?string $dsl = null): string
{
$dsl ??= 'model
schema 1.1
type user
type document
relations
define reader: [user]
define writer: [user]
define owner: [user]';
$client = getTestClient();
$modelId = null;
$authModel = null;
// Parse DSL
$client->dsl($dsl)
->success(function ($model) use (&$authModel): void {
$authModel = $model;
})
->failure(function ($error): void {
throw new RuntimeException('Failed to parse DSL: ' . $error->getMessage());
});
if (! $authModel) {
throw new RuntimeException('Failed to parse DSL');
}
// Create model
$client->createAuthorizationModel(
store: $storeId,
typeDefinitions: $authModel->getTypeDefinitions(),
conditions: $authModel->getConditions(),
)
->success(function ($response) use (&$modelId): void {
$modelId = $response->getModel();
})
->failure(function ($error): void {
throw new RuntimeException('Failed to create test model: ' . $error->getMessage());
});
if (! $modelId) {
throw new RuntimeException('Failed to create test model');
}
return $modelId;
}
// Helper to get a fresh test store for each test
function setupTestStore(): string
{
$storeId = createTestStore();
// Store the ID for cleanup in Pest's afterEach hook
test()->testStoreId = $storeId;
return $storeId;
}
// Helper to get a test store with a model
function setupTestStoreWithModel(?string $dsl = null): array
{
$storeId = setupTestStore();
$modelId = createTestModel($storeId, $dsl);
return ['store' => $storeId, 'model' => $modelId];
}