Oihana PHP Arango

ArangoTestClientsCommand extends Kernel uses ArangoClientTestTrait

Live end-to-end integration test for the new `api/src/oihana/arango/clients/` client.

Talks to a real ArangoDB server (whose connection settings come from the project's [arango] config, with CLI overrides) and exercises the full surface of the client: connection, database lifecycle, collection lifecycle, document CRUD, edges, AQL + cursor, indexes, and error mapping.

Every step runs on its own ephemeral database (arango_clients_test_<random>) created at setup and dropped at cleanup — production data is never touched. The cleanup is in a finally block so the database is dropped even on unexpected exception. Pass --no-cleanup to keep the database around for post-mortem inspection.

Coverage matrix:

Step Surface
0 (setup) build client, create test database
1 server: version + time + availability + listDatabases
2 database: exists, collections()
3 collection: create/properties/rename/drop
4 documents: insert/get/exists/count/update/replace/remove/truncate
5 edge collection: create + inEdges/outEdges/edges
6 AQL + Cursor (single batch + lazy multi-batch) + pipeline + explain / parse
7 indexes: PersistentIndex (unique/sparse) + TtlIndex + drop
8 error mapping: HttpException on 404, ConflictException on 409
9 auth: login / useBearerAuth / useBasicAuth
10 import: bulk JSON Lines + onDuplicate + overwrite + details
11 transactions: begin / commit / abort / status / exists / step / list / withTransaction
12 graphs: create / get / exists / vertex+edge collection mgmt / edge definition mgmt / drop
13 analyzers: 4 types CRUD (identity/text/norm/stem) + listing + get round-trip + drop force
14 views (arangosearch): create + links + AQL SEARCH/PHRASE/BM25 + properties round-trip + drop

Usage:

composer test:clients
composer test:clients -- --step=1-3
composer test:clients -- --step=4
composer test:clients -- --no-cleanup --endpoint=tcp://127.0.0.1:8529

# Long form (equivalent — bypasses Composer):
php bin/console.php command:arango:test:clients --step=4
Tags
author

Marc Alcaraz (ekameleon)

since
1.0.0

Table of Contents

Constants

ARANGO_CONFIG  : string = 'arangoConfig'
DI key carrying the `[arango]` configuration array.
MAX_STEP  : int = 14
Total number of business steps exposed by the `--step` option.
NAME  : string = 'command:arango:test:clients'
The default name of the command.
OPTION_DATABASE  : string = 'database'
OPTION_ENDPOINT  : string = 'endpoint'
OPTION_NO_CLEANUP  : string = 'no-cleanup'
OPTION_PASSWORD  : string = 'password'
OPTION_STEP  : string = 'step'
Name of the `--step` option used to select a subset of steps to run.
OPTION_USER  : string = 'user'
TEST_DB_PREFIX  : string = 'arango_clients_test_'
Prefix of the ephemeral database created for the run.

Properties

$arangoConfig  : array<string, mixed>
Resolved configuration array, captured at construction time.

Methods

__construct()  : mixed
buildArangoClient()  : ArangoClient|null
Builds an {@see ArangoClient} from the trait's stored config, applying any CLI override from `$input`. Reports the resolved endpoint / user / database to `$io` for transparency. Returns null when the config is missing or incomplete.
check()  : array{0: int, 1: int}
Reports a single assertion to `$io` and returns the updated `[ passed , errors ]` counters.
configure()  : void
Configures the current command.
configureArangoTestOptions()  : void
Adds the trait's CLI options to the consuming command.
execute()  : int
Executes the current command.
initializeArangoTestClient()  : void
Captures the `[arango]` configuration injected through the command's `init` array. Called from the consuming command's constructor.
runCleanup()  : void
runSetup()  : array{0: int, 1: int, 2: array}
runStep1()  : array{0: int, 1: int, 2: array}
runStep10()  : array<string|int, mixed>
Exercises {@see \oihana\arango\clients\collection\Collection::import()} — the bulk-load fast path that streams JSON Lines to the dedicated `/_api/import` endpoint.
runStep11()  : array<string|int, mixed>
Exercises the streaming-transaction surface shipped in Lot 7.0b:
runStep12()  : array<string|int, mixed>
Exercises the named-graph surface shipped in Lot 7.1a:
runStep13()  : array<string|int, mixed>
Exercises the ArangoSearch analyzer surface shipped in Lot 7.2a:
runStep14()  : array<string|int, mixed>
Exercises the ArangoSearch view surface shipped in Lot 7.2b plus the resulting full-text query path through AQL `SEARCH`:
runStep2()  : array<string|int, mixed>
runStep3()  : array<string|int, mixed>
runStep4()  : array<string|int, mixed>
runStep5()  : array<string|int, mixed>
runStep6()  : array<string|int, mixed>
runStep7()  : array<string|int, mixed>
runStep8()  : array<string|int, mixed>
runStep9()  : array<string|int, mixed>
Exercises the runtime auth surface added in Lot 6.2c: - `login(user, password)` against `/_open/auth` returns a JWT and makes the transport carry it on subsequent requests, - a second {@see ArangoClient} built bearer-only (no basic credentials) can talk to the server through the same JWT, - `useBearerAuth(null)` falls back to the configured basic credentials, - `useBasicAuth(user, password)` switches the identity at runtime.
shouldCleanup()  : bool
Returns true when `--no-cleanup` is NOT set.
stringOption()  : string|null
Reads a string CLI option, returning null when missing or empty.
waitForSearchHits()  : array<int, string>
Polls an AQL `SEARCH` query until it returns the expected number of rows or the deadline elapses (~2 seconds in 10 attempts of 200 ms).

Constants

ARANGO_CONFIG

DI key carrying the `[arango]` configuration array.

public string ARANGO_CONFIG = 'arangoConfig'

Forwarded through the init array at construction time. The trait reads it via initializeArangoTestClient() and keeps the resolved options for later builds.

OPTION_STEP

Name of the `--step` option used to select a subset of steps to run.

public string OPTION_STEP = 'step'

TEST_DB_PREFIX

Prefix of the ephemeral database created for the run.

private string TEST_DB_PREFIX = 'arango_clients_test_'

Properties

$arangoConfig

Resolved configuration array, captured at construction time.

protected array<string, mixed> $arangoConfig = []

Methods

__construct()

public __construct(string|null $name[, Container|null $container = null ][, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$name : string|null
$container : Container|null = null
$init : array<string|int, mixed> = []
Tags
throws
DependencyException
NotFoundException
ContainerExceptionInterface
NotFoundExceptionInterface

buildArangoClient()

Builds an {@see ArangoClient} from the trait's stored config, applying any CLI override from `$input`. Reports the resolved endpoint / user / database to `$io` for transparency. Returns null when the config is missing or incomplete.

protected buildArangoClient(InputInterface $input, SymfonyStyle $io) : ArangoClient|null
Parameters
$input : InputInterface
$io : SymfonyStyle
Return values
ArangoClient|null

check()

Reports a single assertion to `$io` and returns the updated `[ passed , errors ]` counters.

protected check(SymfonyStyle $io, mixed $condition, string $label, int $passed, int $errors) : array{0: int, 1: int}

Same contract as the auth:test:* ApiAssertionsTrait::check(), inlined here so the new arango command does not depend on the auth namespace.

Parameters
$io : SymfonyStyle
$condition : mixed
$label : string
$passed : int
$errors : int
Return values
array{0: int, 1: int}

configureArangoTestOptions()

Adds the trait's CLI options to the consuming command.

protected configureArangoTestOptions() : void

execute()

Executes the current command.

protected execute(InputInterface $input, OutputInterface $output) : int
Parameters
$input : InputInterface
$output : OutputInterface
Tags
throws
RandomException
Return values
int

initializeArangoTestClient()

Captures the `[arango]` configuration injected through the command's `init` array. Called from the consuming command's constructor.

protected initializeArangoTestClient(array<string, mixed> $init) : void
Parameters
$init : array<string, mixed>

runSetup()

protected runSetup(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array{0: int, 1: int, 2: array}
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array{0: int, 1: int, 2: array}

runStep1()

protected runStep1(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array{0: int, 1: int, 2: array}
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array{0: int, 1: int, 2: array}

runStep10()

Exercises {@see \oihana\arango\clients\collection\Collection::import()} — the bulk-load fast path that streams JSON Lines to the dedicated `/_api/import` endpoint.

protected runStep10(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
  • 3 documents imported through a plain import() populate the collection and return created: 3, every other counter at 0,
  • onDuplicate: ignore lets the same batch be re-imported with the duplicates surfaced through the ignored counter,
  • onDuplicate: update patches the existing documents (the server bumps updated) and the change is observable through a subsequent document() round-trip,
  • onDuplicate: error + details: true surfaces every duplicate as a server-side error message in ImportResult::$details,
  • overwrite: true truncates the collection before importing — Collection::count() confirms the previous content is gone,
  • an empty input still hits the server (no client-side short-circuit) and produces a zeroed ImportResult.
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
JsonException
Return values
array<string|int, mixed>

runStep11()

Exercises the streaming-transaction surface shipped in Lot 7.0b:

protected runStep11(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
  • Database::beginTransaction() returns a Transaction whose id is the server-assigned id and whose initial status() is running.
  • Transaction::exists() is true for the live trx and false for a bogus id.
  • Transaction::step() propagates the x-arango-trx-id header transparently: a Collection::insert() called inside the callback is part of the transaction (an outside reader does NOT see the pending row through the server-side count — this would only be testable with strict isolation; we instead verify the row IS visible inside the transaction).
  • Transaction::commit() makes the staged write durable.
  • A second transaction with abort() discards its staged write — the count is unchanged after the abort.
  • Database::listTransactions() includes the running trx while it is open.
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Throwable
Return values
array<string|int, mixed>

runStep12()

Exercises the named-graph surface shipped in Lot 7.1a:

protected runStep12(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
  • Database::createGraph() posts a graph with edge definitions and returns a Graph handle.
  • Graph::exists() / get() / edgeDefinitions() / vertexCollections() / edgeCollections() / orphanCollections() reflect the server state.
  • Database::graphs() / listGraphs() include the freshly created graph.
  • Vertex collection management (addVertexCollection / removeVertexCollection) lives on its own (the collection becomes an "orphan" until referenced by an edge definition).
  • Edge definition management (addEdgeDefinition / replaceEdgeDefinition / removeEdgeDefinition).
  • Graph::drop(dropCollections: true) wipes the graph AND its underlying vertex/edge collections.

The vertex/edge CRUD (insert/get/replace/update/remove via gharial endpoints) lands separately on GraphVertexCollection / GraphEdgeCollection in Lot 7.1b — Step 12 will be enriched then.

Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
RandomException
Return values
array<string|int, mixed>

runStep13()

Exercises the ArangoSearch analyzer surface shipped in Lot 7.2a:

protected runStep13(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
  • Database::createAnalyzer() posts an analyzer with typed options and returns an Analyzer handle.
  • All four V1 must-have types (identity, text, norm, stem) round-trip through Analyzer::get() with their properties preserved server-side.
  • Analyzer::exists() distinguishes a created analyzer from a missing one (404 swallowed cleanly).
  • Database::listAnalyzers() and Database::analyzers() include the user-created entries (each name prefixed with the test database name server-side, e.g. mydb::raw).
  • Analyzer::drop() removes the analyzer, with and without the force flag (force is harmless when no view / inverted index references the analyzer).

The arangosearch View + AQL SEARCH live coverage lands separately in Step 14 with Lot 7.2b.

Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
RandomException
Return values
array<string|int, mixed>

runStep14()

Exercises the ArangoSearch view surface shipped in Lot 7.2b plus the resulting full-text query path through AQL `SEARCH`:

protected runStep14(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
  • Database::createView() posts an arangosearch view with ArangoSearchLink typed links and returns a View handle.
  • View::get() returns the simple description (4 top-level fields); View::properties() returns the full per-view configuration including the normalised links echo.
  • Database::views() / listViews() include the freshly created view.
  • The actual indexing path: insert documents in a linked collection, then run AQL SEARCH ANALYZER(...) and SEARCH PHRASE(...) queries through the view; both must return the expected documents. BM25() scoring round-trips too.
  • View::updateProperties() (PATCH) bumps the cleanupIntervalStep; properties() echoes the new value.
  • View::drop() removes the view (the source collection itself is untouched).

ArangoSearch indexes documents asynchronously — the view created here is configured with short commitIntervalMsec / consolidationIntervalMsec so the queries can be polled inside a short window. waitForSearchHits() retries the SEARCH up to ~2 seconds before giving up.

Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
RandomException
Return values
array<string|int, mixed>

runStep2()

protected runStep2(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep3()

protected runStep3(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep4()

protected runStep4(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep5()

protected runStep5(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep6()

protected runStep6(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep7()

protected runStep7(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep8()

protected runStep8(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>
Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

runStep9()

Exercises the runtime auth surface added in Lot 6.2c: - `login(user, password)` against `/_open/auth` returns a JWT and makes the transport carry it on subsequent requests, - a second {@see ArangoClient} built bearer-only (no basic credentials) can talk to the server through the same JWT, - `useBearerAuth(null)` falls back to the configured basic credentials, - `useBasicAuth(user, password)` switches the identity at runtime.

protected runStep9(SymfonyStyle $io, ArangoClient $client, array<string|int, mixed> $state, int $passed, int $errors) : array<string|int, mixed>

Skipped (with a single passing assertion explaining why) when no basic credentials are configured — /_open/auth requires them.

Parameters
$io : SymfonyStyle
$client : ArangoClient
$state : array<string|int, mixed>
$passed : int
$errors : int
Tags
throws
ArangoException
Return values
array<string|int, mixed>

shouldCleanup()

Returns true when `--no-cleanup` is NOT set.

protected shouldCleanup(InputInterface $input) : bool
Parameters
$input : InputInterface
Return values
bool

stringOption()

Reads a string CLI option, returning null when missing or empty.

private stringOption(InputInterface $input, string $name) : string|null
Parameters
$input : InputInterface
$name : string
Return values
string|null

waitForSearchHits()

Polls an AQL `SEARCH` query until it returns the expected number of rows or the deadline elapses (~2 seconds in 10 attempts of 200 ms).

private waitForSearchHits(Database $db, AqlQuery $query, int $expectedCount) : array<int, string>

ArangoSearch indexes documents asynchronously: a freshly inserted doc is not immediately visible to SEARCH. The view is configured with short commit / consolidation intervals in Step 14, but a small wait is still needed.

Parameters
$db : Database
$query : AqlQuery
$expectedCount : int
Tags
throws
ArangoException
Return values
array<int, string>

The result rows (typically _key strings).

On this page

Search results