2026-02-07 20:11:15 +01:00
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) /.. " && pwd ) "
# shellcheck source=/dev/null
source " ${ ROOT_DIR } /src/app_v2.sh "
require_jq( ) {
if ! command -v jq >/dev/null 2>& 1; then
echo "jq is required for integration_v2.sh" >& 2
exit 2
fi
}
assert_contains( ) {
local haystack = " $1 "
local needle = " $2 "
if [ [ " ${ haystack } " != *" ${ needle } " * ] ] ; then
echo " assertion failed: expected to find ${ needle } " >& 2
exit 1
fi
}
app_init
require_jq
if [ [ ! -S " ${ SOCK } " ] ] ; then
echo " integration_v2.sh: SKIP (socket not found at ${ SOCK } ) "
exit 77
fi
# 1) startup checks
startup_out = " $( app_startup_checks) "
assert_contains " ${ startup_out } " '"ok"'
# 2) idempotent ingest (batch + continue_on_error)
run_id = " $( date +%s) "
trace_id = " trace-it- ${ run_id } "
idempotency_key = " it-seed- ${ run_id } "
2026-02-07 20:27:12 +01:00
doc_name = " doc-it ${ run_id } "
topic_name = " topic-italpha ${ run_id } "
2026-02-07 20:11:15 +01:00
payload = " $( cat <<JSON
{
"idempotency_key" :" ${ idempotency_key } " ,
"mode" :"continue_on_error" ,
"nodes" :[ { "name" :" ${ doc_name } " } ,{ "name" :" ${ topic_name } " } ] ,
"edges" :[
{
"subject" :" ${ doc_name } " ,
"predicate" :"ms.within_domain" ,
"object" :" ${ topic_name } " ,
"provenance" :{
"source_uri" :"urn:test:seed" ,
"extractor" :"integration-test" ,
"observed_at" :1,
"ingested_at" :2,
"trace_id" :" ${ trace_id } "
}
}
]
}
JSON
) "
ingest_out = " $( app_ingest_batch " ${ payload } " ) "
assert_contains " ${ ingest_out } " '"ok":true'
# Re-submit same idempotency key + identical payload.
ingest_out_2 = " $( app_ingest_batch " ${ payload } " ) "
assert_contains " ${ ingest_out_2 } " '"idempotency_key"'
# 3) incremental sync with durable opaque cursor
rm -f " ${ CURSOR_FILE } "
sync_out = " $( app_sync_once) "
assert_contains " ${ sync_out } " '"events"'
[ [ -s " ${ CURSOR_FILE } " ] ] || { echo "cursor file not persisted" >& 2; exit 1; }
# 4) retrieval endpoint + fallback path available
retrieve_out = " $( app_retrieve_with_fallback " ${ doc_name } " "ms.within_domain" ) "
assert_contains " ${ retrieve_out } " '"edges"'
# Capture edge_ref using subgraph surface to avoid format differences.
subgraph_out = " $( amduat_api_call GET " /v2/graph/subgraph?roots[]= ${ doc_name } &max_depth=2&dir=outgoing&limit_nodes=200&limit_edges=400&include_stats=true&max_result_bytes=1048576 " && printf '%s' " ${ AMDUAT_LAST_BODY } " ) "
edge_ref = " $( printf '%s' " ${ subgraph_out } " | jq -r '.edges[0].edge_ref // empty' ) "
if [ [ -z " ${ edge_ref } " ] ] ; then
echo "failed to resolve edge_ref" >& 2
exit 1
fi
# 5) correction path and tombstone visibility semantics
app_tombstone_edge " ${ edge_ref } " >/dev/null
post_tombstone_retrieve = " $( app_retrieve_with_fallback " ${ doc_name } " "ms.within_domain" ) "
post_edges_count = " $( printf '%s' " ${ post_tombstone_retrieve } " | jq '.edges | length' ) "
if [ [ " ${ post_edges_count } " != "0" ] ] ; then
echo "expected retrieval default to hide tombstoned edges" >& 2
exit 1
fi
visible_tombstone = " $( amduat_api_call GET " /v2/graph/subgraph?roots[]= ${ doc_name } &max_depth=2&dir=outgoing&limit_nodes=200&limit_edges=400&include_tombstoned=true&max_result_bytes=1048576 " && printf '%s' " ${ AMDUAT_LAST_BODY } " ) "
assert_contains " ${ visible_tombstone } " '"edges"'
echo "integration_v2.sh: PASS"