feat: Frappe customer integration tests + FrappeHttpClient get/delete
Adds GET and DELETE methods to FrappeHttpClient. Integration tests cover create, find, not-found (wrong name), and delete against the live staging ERPNext instance. Run with: bin/test-integration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
25cc47e7d6
commit
cba8ebcf5e
3 changed files with 160 additions and 0 deletions
16
bin/test-integration
Executable file
16
bin/test-integration
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Load .env.local secrets and run integration tests inside the app container.
|
||||||
|
if [ -f "$(dirname "$0")/../.env.local" ]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$(dirname "$0")/../.env.local"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose exec \
|
||||||
|
-e FRAPPE_ERP_BASE_URL="${FRAPPE_ERP_BASE_URL:-}" \
|
||||||
|
-e FRAPPE_ERP_API_KEY="${FRAPPE_ERP_API_KEY:-}" \
|
||||||
|
-e FRAPPE_ERP_API_SECRET="${FRAPPE_ERP_API_SECRET:-}" \
|
||||||
|
app php vendor/bin/phpunit tests/Integration/ --testdox "$@"
|
||||||
|
|
@ -42,6 +42,40 @@ class FrappeHttpClient
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET a Frappe resource endpoint.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function get(string $path): array
|
||||||
|
{
|
||||||
|
$response = $this->httpClient->request('GET', $this->baseUrl.$path, [
|
||||||
|
'headers' => ['Authorization' => $this->authHeader],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var array<string, mixed> $result */
|
||||||
|
$result = $response->toArray();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE a Frappe resource.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function delete(string $path): array
|
||||||
|
{
|
||||||
|
$response = $this->httpClient->request('DELETE', $this->baseUrl.$path, [
|
||||||
|
'headers' => ['Authorization' => $this->authHeader],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var array<string, mixed> $result */
|
||||||
|
$result = $response->toArray();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/** GET raw binary content (for PDF downloads). */
|
/** GET raw binary content (for PDF downloads). */
|
||||||
public function getContent(string $path): string
|
public function getContent(string $path): string
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Integration\Infrastructure\Channel\Frappe;
|
||||||
|
|
||||||
|
use App\Infrastructure\Channel\Frappe\FrappeHttpClient;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests against the live ERPNext staging instance.
|
||||||
|
* Requires FRAPPE_ERP_BASE_URL, FRAPPE_ERP_API_KEY, FRAPPE_ERP_API_SECRET in .env.local.
|
||||||
|
*
|
||||||
|
* Run with: vendor/bin/phpunit --testsuite Integration
|
||||||
|
*/
|
||||||
|
final class FrappeCustomerIntegrationTest extends TestCase
|
||||||
|
{
|
||||||
|
private FrappeHttpClient $client;
|
||||||
|
private string $createdCustomerName = '';
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$baseUrl = $_SERVER['FRAPPE_ERP_BASE_URL'] ?? getenv('FRAPPE_ERP_BASE_URL');
|
||||||
|
$apiKey = $_SERVER['FRAPPE_ERP_API_KEY'] ?? getenv('FRAPPE_ERP_API_KEY');
|
||||||
|
$apiSecret = $_SERVER['FRAPPE_ERP_API_SECRET'] ?? getenv('FRAPPE_ERP_API_SECRET');
|
||||||
|
|
||||||
|
if (!$baseUrl || !$apiKey || !$apiSecret) {
|
||||||
|
$this->markTestSkipped('FRAPPE_ERP_* env vars not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client = new FrappeHttpClient(
|
||||||
|
HttpClient::create(),
|
||||||
|
(string) $baseUrl,
|
||||||
|
(string) $apiKey,
|
||||||
|
(string) $apiSecret,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
if ('' !== $this->createdCustomerName) {
|
||||||
|
try {
|
||||||
|
$this->client->delete('/api/resource/Customer/'.$this->createdCustomerName);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// best-effort cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_customer(): void
|
||||||
|
{
|
||||||
|
$response = $this->client->post('/api/resource/Customer', [
|
||||||
|
'customer_name' => 'Test Superseller Integration',
|
||||||
|
'customer_type' => 'Individual',
|
||||||
|
'customer_group' => 'Individual',
|
||||||
|
'territory' => 'Germany',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('data', $response);
|
||||||
|
$this->assertNotEmpty($response['data']['name']);
|
||||||
|
|
||||||
|
$this->createdCustomerName = $response['data']['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_created_customer(): void
|
||||||
|
{
|
||||||
|
// Create first
|
||||||
|
$created = $this->client->post('/api/resource/Customer', [
|
||||||
|
'customer_name' => 'Test Superseller Integration',
|
||||||
|
'customer_type' => 'Individual',
|
||||||
|
'customer_group' => 'Individual',
|
||||||
|
'territory' => 'Germany',
|
||||||
|
]);
|
||||||
|
$this->createdCustomerName = $created['data']['name'];
|
||||||
|
|
||||||
|
// Find by name
|
||||||
|
$response = $this->client->get('/api/resource/Customer/'.$this->createdCustomerName);
|
||||||
|
|
||||||
|
$this->assertSame($this->createdCustomerName, $response['data']['name']);
|
||||||
|
$this->assertSame('Test Superseller Integration', $response['data']['customer_name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_nonexistent_customer_throws(): void
|
||||||
|
{
|
||||||
|
$this->expectException(ClientExceptionInterface::class);
|
||||||
|
|
||||||
|
$this->client->get('/api/resource/Customer/CUST-DOES-NOT-EXIST-99999');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_delete_customer(): void
|
||||||
|
{
|
||||||
|
// Create
|
||||||
|
$created = $this->client->post('/api/resource/Customer', [
|
||||||
|
'customer_name' => 'Test Superseller Integration',
|
||||||
|
'customer_type' => 'Individual',
|
||||||
|
'customer_group' => 'Individual',
|
||||||
|
'territory' => 'Germany',
|
||||||
|
]);
|
||||||
|
$name = $created['data']['name'];
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
$this->client->delete('/api/resource/Customer/'.$name);
|
||||||
|
|
||||||
|
// Confirm gone
|
||||||
|
$this->expectException(ClientExceptionInterface::class);
|
||||||
|
$this->client->get('/api/resource/Customer/'.$name);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue