2026-09-06 00:17:20 +03:00
use super ::* ;
use oa_core ::source_project ::* ;
use std ::collections ::BTreeMap ;
fn authored () -> SourceBundle {
let bundle = SourceBundle {
entry : "harbor.arcadia" . into (),
files : BTreeMap ::from ([
2026-09-06 03:46:13 +03:00
( "harbor.arcadia" . into (), "// Harbor engineering rationale 航海 \r\n arcadia 4; project harbor \" Harbor rescue \" { \r\n include \" team/first.arcadia \" ; \r\n include \" team/second.arcadia \" ; \r\n include \" contracts.arcadia \" ; \r\n include \" work.arcadia \" ; \r\n } \r\n " . into ()),
2026-09-06 00:17:20 +03:00
( "team/first.arcadia" . into (), "// First payload rationale \r\n sa_exchange_item first \" First payload \" { description \" Reviewed first \" ; } \r\n " . into ()),
( "team/second.arcadia" . into (), "// Second payload rationale \r\n sa_exchange_item second \" Second payload \" {} \r\n " . into ()),
2026-09-06 03:46:13 +03:00
( "contracts.arcadia" . into (), "// Contract ownership stays here \r\n sa_interface contract \" Contract \" {} \r\n interface_item_allocation first_allocation \" First allocation \" { source @contract; target @first // first reference rationale \r\n ; order 0; } \r\n interface_item_allocation second_allocation \" Second allocation \" { source @contract; target @second // second reference rationale \r\n ; order 1; } \r\n " . into ()),
2026-09-06 00:17:20 +03:00
( "work.arcadia" . into (), "// Operational rationale \r\n oa_activity assess \" Assess assistance \" {} \r\n " . into ()),
]),
};
compile_bundle ( & bundle , ProjectLimits ::default ())
. unwrap ()
. bundle
}
fn id_for ( model : & Project , alias : & str ) -> Id {
model
. identities ()
. into_iter ()
. find ( | ( _ , name ) | * name == alias )
. unwrap ()
. 0
}
fn assert_pair ( stored : & StoredProject ) -> CompiledSourceProject {
let compiled = compile_bundle ( & stored . source_project , ProjectLimits ::default ()). unwrap ();
assert_eq! ( compiled . decoded . project , stored . model );
assert_eq! (
compiled . bundle , stored . source_project ,
"Stored IDs already stabilized"
);
compiled
}
async fn import ( server : & TestServer , bundle : & SourceBundle ) -> StoredProject {
let response = server
. client
. post ( server . url ( "/projects/import-source-project" ))
. json ( bundle )
. send ()
. await
. unwrap ();
let status = response . status ();
let body : Value = response . json (). await . unwrap ();
assert_eq! ( status , StatusCode ::CREATED , "{body}" );
server
. get ( body [ "resource_id" ]. as_str (). unwrap (). parse (). unwrap ())
. await
}
async fn put ( server : & TestServer , current : & StoredProject , bundle : & SourceBundle ) -> Value {
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {} /source-project" , current . resource_id )))
. header ( "if-match" , current . etag ())
. json ( bundle )
. send ()
. await
. unwrap ();
let status = response . status ();
let body : Value = response . json (). await . unwrap ();
assert_eq! ( status , StatusCode ::OK , "{body}" );
body
}
async fn transact (
server : & TestServer ,
current : & StoredProject ,
commands : & [ Command ],
dry_run : bool ,
) -> Value {
let response = server
. client
. post ( server . url ( & format! ( "/projects/ {} /transactions" , current . resource_id )))
. header ( "if-match" , current . etag ())
. json ( & json! ({ "commands" :commands , "dry_run" :dry_run }))
. send ()
. await
. unwrap ();
let status = response . status ();
let body : Value = response . json (). await . unwrap ();
assert_eq! ( status , StatusCode ::OK , "{body}" );
body
}
#[tokio::test]
async fn source_projects_preserve_exact_files_comments_and_comment_only_revisions () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let source = authored ();
let original = import ( & server , & source ). await ;
let id = original . resource_id ;
assert_eq! ( original . source_project , source );
assert_pair ( & original );
let url = server . url ( & format! ( "/projects/ {id} /source-project" ));
let exported = server . client . get ( & url ). send (). await . unwrap ();
assert_eq! (
exported . headers ()[ "etag" ]. to_str (). unwrap (),
original . etag ()
);
assert_eq! ( exported . json ::< SourceBundle > (). await . unwrap (), source );
assert_eq! (
server
. client
. get ( & url )
. header ( "if-none-match" , original . etag ())
. send ()
. await
. unwrap ()
. status (),
StatusCode ::NOT_MODIFIED
);
let mut edited = source . clone ();
edited
. files
. get_mut ( "team/first.arcadia" )
. unwrap ()
. push_str ( "// Reviewed through automation \r\n " );
let response = put ( & server , & original , & edited ). await ;
assert_eq! ( response [ "changed" ], true );
assert_eq! (
response [ "source_project" ],
serde_json ::to_value ( & edited ). unwrap ()
);
let current = server . get ( id ). await ;
assert_eq! ( current . revision , 2 );
assert_eq! ( current . model , original . model );
assert_eq! ( current . source_project , edited );
let stale = server
. client
. post ( server . url ( & format! ( "/projects/ {id} /transactions" )))
. header ( "if-match" , original . etag ())
. json ( & json! ({ "commands" :[ Command ::Rename ( current . model . id , "Stale change" . into ())]}))
. send ()
. await
. unwrap ();
assert_eq! ( stale . status (), StatusCode ::PRECONDITION_FAILED );
let filename = directory . path (). join ( format! ( " {id} .json" ));
let saved_bytes = std ::fs ::read ( & filename ). unwrap ();
let no_op = put ( & server , & current , & edited ). await ;
assert_eq! ( no_op [ "changed" ], false );
assert_eq! ( no_op [ "revision" ], 2 );
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), saved_bytes );
let mut renamed = edited ;
let text = renamed . files . remove ( & renamed . entry ). unwrap ();
renamed . entry = "rescue.arcadia" . into ();
renamed . files . insert ( renamed . entry . clone (), text );
assert_eq! ( put ( & server , & current , & renamed ). await [ "changed" ], true );
let current = server . get ( id ). await ;
assert_eq! ( current . revision , 3 );
assert_eq! ( current . model , original . model );
server . stop (). await ;
let server = TestServer ::start ( directory . path (), None ). await ;
let restored = server . get ( id ). await ;
assert_eq! ( restored . source_project , renamed );
assert_eq! ( restored . etag (), current . etag ());
assert_pair ( & restored );
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_failed_atomic_comment_write_preserves_both_representations () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let current = import ( & server , & authored ()). await ;
let id = current . resource_id ;
let envelope = directory . path (). join ( format! ( " {id} .json" ));
let bytes = std ::fs ::read ( & envelope ). unwrap ();
let mut edited = current . source_project . clone ();
edited
. files
. get_mut ( "work.arcadia" )
. unwrap ()
. push_str ( "// Must not be adopted \r\n " );
std ::fs ::remove_file ( & envelope ). unwrap ();
std ::fs ::create_dir ( & envelope ). unwrap ();
let failed = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source-project" )))
. header ( "if-match" , current . etag ())
. json ( & edited )
. send ()
. await
. unwrap ();
assert_eq! ( failed . status (), StatusCode ::SERVICE_UNAVAILABLE );
let unchanged = server . get ( id ). await ;
assert_eq! ( unchanged . etag (), current . etag ());
assert_eq! ( unchanged . model , current . model );
assert_eq! ( unchanged . source_project , current . source_project );
std ::fs ::remove_dir ( & envelope ). unwrap ();
std ::fs ::write ( & envelope , & bytes ). unwrap ();
assert! ( ! std ::fs ::read_dir ( directory . path ()). unwrap (). any ( | entry | {
entry
. unwrap ()
. file_name ()
. to_string_lossy ()
. starts_with ( ".arcadia-write-" )
}));
server . stop (). await ;
let server = TestServer ::start ( directory . path (), None ). await ;
assert_eq! ( server . get ( id ). await . source_project , current . source_project );
server . stop (). await ;
}
#[test]
fn source_projects_openapi_describes_the_coupled_envelope_and_source_diagnostics () {
let spec : Value = serde_json ::from_str ( include_str! ( "../../../../docs/openapi.json" )). unwrap ();
let schemas = & spec [ "components" ][ "schemas" ];
assert! (
schemas [ "StoredProject" ][ "required" ]
. as_array ()
. unwrap ()
. contains ( & json! ( "source_project" ))
);
assert_eq! (
schemas [ "SourceBundle" ][ "required" ],
json! ([ "entry" , "files" ])
);
assert_eq! ( schemas [ "SourceBundle" ][ "additionalProperties" ], false );
assert_eq! (
schemas [ "SourceBundle" ][ "properties" ][ "files" ][ "maxProperties" ],
256
);
for property in [ "path" , "span" , "related" ] {
assert! ( schemas [ "Issue" ][ "properties" ]. get ( property ). is_some ());
}
assert_eq! (
schemas [ "TransactionResult" ][ "properties" ][ "source_project" ][ "$ref" ],
"#/components/schemas/SourceBundle"
);
assert! (
schemas [ "ValidationRequest" ][ "oneOf" ]
. as_array ()
. unwrap ()
. iter ()
. any ( | candidate | candidate [ "properties" ][ "format" ][ "const" ] == "source-project" )
);
let routes = & spec [ "paths" ];
assert_eq! (
routes [ "/api/v1/projects/import-source-project" ][ "post" ][ "requestBody" ][ "content" ][ "application/json" ]
[ "schema" ][ "$ref" ],
"#/components/schemas/SourceBundle"
);
assert_eq! (
routes [ "/api/v1/projects/{id}/source-project" ][ "get" ][ "responses" ][ "200" ][ "content" ][ "application/json" ]
[ "schema" ][ "$ref" ],
"#/components/schemas/SourceBundle"
);
assert_eq! (
routes [ "/api/v1/projects/{id}/source-project" ][ "put" ][ "responses" ][ "200" ][ "content" ][ "application/json" ]
[ "schema" ][ "$ref" ],
"#/components/schemas/SourceProjectMutationResult"
);
}
#[tokio::test]
async fn source_projects_command_dry_runs_and_identity_swaps_preserve_lexical_ownership () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let original = import ( & server , & authored ()). await ;
let first = id_for ( & original . model , "first" );
let second = id_for ( & original . model , "second" );
let changed_id = Id ::new_v4 ();
let mut element = original . model . elements [ & first ]. clone ();
element . id = changed_id ;
element . alias = "revised_first" . into ();
element . name = "Reviewed payload" . into ();
let commands = vec! [ Command ::EditElement ( first , element )];
let filename = directory
. path ()
. join ( format! ( " {} .json" , original . resource_id ));
let before_bytes = std ::fs ::read ( & filename ). unwrap ();
let dry_run = transact ( & server , & original , & commands , true ). await ;
let proposed : SourceBundle = serde_json ::from_value ( dry_run [ "source_project" ]. clone ()). unwrap ();
assert! ( proposed . files [ "team/first.arcadia" ]. contains ( & changed_id . to_string ()));
assert! ( proposed . files [ "team/first.arcadia" ]. starts_with ( "// First payload rationale \r\n " ));
assert_eq! (
proposed . files [ "team/second.arcadia" ],
original . source_project . files [ "team/second.arcadia" ]
);
assert! (
proposed . files [ "contracts.arcadia" ]. contains ( "@revised_first // first reference rationale" )
);
assert_eq! (
server . get ( original . resource_id ). await . source_project ,
original . source_project
);
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), before_bytes );
transact ( & server , & original , & commands , false ). await ;
let current = server . get ( original . resource_id ). await ;
assert_eq! ( current . source_project , proposed );
assert_eq! (
assert_pair ( & current ). index . declarations [ & changed_id ]. path ,
"team/first.arcadia"
);
let temporary = Id ::new_v4 ();
let swaps = vec! [ Command ::Batch ( vec! [
Command ::Reidentify ( changed_id , temporary ),
Command ::Reidentify ( second , changed_id ),
Command ::Reidentify ( temporary , second ),
])];
transact ( & server , & current , & swaps , false ). await ;
let swapped = server . get ( current . resource_id ). await ;
let compiled = assert_pair ( & swapped );
assert_eq! (
compiled . index . declarations [ & second ]. path ,
"team/first.arcadia"
);
assert_eq! (
compiled . index . declarations [ & changed_id ]. path ,
"team/second.arcadia"
);
assert! (
swapped . source_project . files [ "team/first.arcadia" ]
. starts_with ( "// First payload rationale \r\n " )
);
assert! (
swapped . source_project . files [ "contracts.arcadia" ]
. contains ( "@revised_first // first reference rationale" )
);
// Removing and reusing a UUID does not attach the old authored declaration
// and its rationale to an unrelated new record.
let mut replacement = swapped . model . elements [ & second ]. clone ();
replacement . alias = "new_payload" . into ();
replacement . name = "Independent new payload" . into ();
replacement . description . clear ();
transact (
& server ,
& swapped ,
& [ Command ::Delete ( second ), Command ::CreateElement ( replacement )],
false ,
)
. await ;
let replaced = server . get ( swapped . resource_id ). await ;
let compiled = assert_pair ( & replaced );
assert_ne! (
compiled . index . declarations [ & second ]. path ,
"team/first.arcadia"
);
assert! (
replaced . source_project . files [ "team/first.arcadia" ]. contains ( "First payload rationale" )
);
assert! ( ! replaced . source_project . files [ "team/first.arcadia" ]. contains ( "new_payload" ));
server . stop (). await ;
let server = TestServer ::start ( directory . path (), None ). await ;
assert_eq! (
server . get ( replaced . resource_id ). await . source_project ,
replaced . source_project
);
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_equal_models_still_apply_changed_identity_provenance () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let original = import ( & server , & authored ()). await ;
let work = id_for ( & original . model , "assess" );
let commands = [
Command ::Delete ( work ),
Command ::CreateElement ( original . model . elements [ & work ]. clone ()),
];
let prepared =
oa_core ::prepare_command ( & original . model , & Command ::Batch ( commands . to_vec ())). unwrap ();
assert_eq! ( prepared . project , original . model );
assert! ( ! prepared . identity_map . contains_key ( & work ));
let dry_run = transact ( & server , & original , & commands , true ). await ;
assert_eq! (
dry_run [ "changed" ], true ,
"Recreated record must not inherit the deleted record's declaration ownership"
);
assert_eq! (
dry_run [ "model" ],
serde_json ::to_value ( & original . model ). unwrap ()
);
let response = transact ( & server , & original , & commands , false ). await ;
assert_eq! ( response [ "changed" ], true );
let changed = server . get ( original . resource_id ). await ;
assert_eq! ( changed . revision , original . revision + 1 );
assert_eq! ( changed . model , original . model );
let compiled = assert_pair ( & changed );
assert_ne! ( compiled . index . declarations [ & work ]. path , "work.arcadia" );
assert! ( changed . source_project . files [ "work.arcadia" ]. contains ( "Operational rationale" ));
assert! ( ! changed . source_project . files [ "work.arcadia" ]. contains ( "oa_activity" ));
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_model_interchange_and_transitions_keep_authored_modules () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let original = import ( & server , & authored ()). await ;
let id = original . resource_id ;
let work = id_for ( & original . model , "assess" );
let mut model = original . model . clone ();
model . elements . get_mut ( & work ). unwrap (). name = "Review assistance" . into ();
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /model" )))
. header ( "if-match" , original . etag ())
. json ( & model )
. send ()
. await
. unwrap ();
assert_eq! (
response . status (),
StatusCode ::OK ,
"{}" ,
response . text (). await . unwrap ()
);
let current = server . get ( id ). await ;
assert_eq! ( current . source_project . entry , "harbor.arcadia" );
assert_eq! (
current . source_project . files [ "team/first.arcadia" ],
original . source_project . files [ "team/first.arcadia" ]
);
assert! (
current . source_project . files [ "work.arcadia" ]. starts_with ( "// Operational rationale \r\n " )
);
model . elements . get_mut ( & work ). unwrap (). description =
"Changed through canonical interchange" . into ();
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source" )))
. header ( "if-match" , current . etag ())
. header ( "content-type" , "text/plain" )
. body ( oa_core ::language ::format ( & model ))
. send ()
. await
. unwrap ();
assert_eq! (
response . status (),
StatusCode ::OK ,
"{}" ,
response . text (). await . unwrap ()
);
let current = server . get ( id ). await ;
assert_pair ( & current );
let request = oa_core ::transitions ::TransitionRequest ::new (
oa_core ::transitions ::TransitionDirection ::OaToSa ,
vec! [ work ],
);
let response = server
. client
. post ( server . url ( & format! ( "/projects/ {id} /transitions/preview" )))
. header ( "if-match" , current . etag ())
. json ( & request )
. send ()
. await
. unwrap ();
let status = response . status ();
let preview : Value = response . json (). await . unwrap ();
assert_eq! ( status , StatusCode ::OK , "{preview}" );
assert_eq! ( server . get ( id ). await . source_project , current . source_project );
let response = server
. client
. post ( server . url ( & format! ( "/projects/ {id} /transitions/apply" )))
. header ( "if-match" , current . etag ())
. json ( & preview )
. send ()
. await
. unwrap ();
assert_eq! (
response . status (),
StatusCode ::OK ,
"{}" ,
response . text (). await . unwrap ()
);
let final_state = server . get ( id ). await ;
assert_pair ( & final_state );
assert! (
final_state
. model
. elements
. values ()
. any ( | e | e . kind () == Kind ::Function )
);
assert_eq! ( final_state . model . traces . len (), 1 );
for path in [
"team/first.arcadia" ,
"team/second.arcadia" ,
"contracts.arcadia" ,
] {
assert_eq! (
final_state . source_project . files [ path ],
original . source_project . files [ path ]
);
}
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_invalid_dependencies_and_duplicate_paths_never_change_the_head () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let current = import ( & server , & authored ()). await ;
let id = current . resource_id ;
let filename = directory . path (). join ( format! ( " {id} .json" ));
let original_bytes = std ::fs ::read ( & filename ). unwrap ();
// A same-named server file cannot satisfy a logical source include.
std ::fs ::write (
directory . path (). join ( "server_only.arcadia" ),
"oa_activity external \" Must not load \" {} \n " ,
)
. unwrap ();
let mut cases = vec! [];
let mut missing = current . source_project . clone ();
missing
. files
. get_mut ( "work.arcadia" )
. unwrap ()
. push_str ( "include \" server_only.arcadia \" ; \n " );
cases . push (( missing , "missing_include" ));
let mut duplicate = current . source_project . clone ();
let first = duplicate . files [ "team/first.arcadia" ]. clone ();
duplicate
. files
. get_mut ( "team/second.arcadia" )
. unwrap ()
. push_str ( & first );
cases . push (( duplicate , "duplicate_declaration" ));
let mut invalid = current . source_project . clone ();
invalid
. files
. get_mut ( "team/second.arcadia" )
. unwrap ()
. push ( '?' );
cases . push (( invalid , "" ));
let mut cycle = current . source_project . clone ();
cycle
. files
. get_mut ( "work.arcadia" )
. unwrap ()
. push_str ( "include \" harbor.arcadia \" ; \n " );
cases . push (( cycle , "include_cycle" ));
let mut orphan = current . source_project . clone ();
orphan
. files
. insert ( "orphan.arcadia" . into (), "// not included \n " . into ());
cases . push (( orphan , "unreachable_file" ));
let mut escaped = current . source_project . clone ();
escaped
. files
. insert ( "../outside.arcadia" . into (), String ::new ());
cases . push (( escaped , "invalid_path" ));
let mut excessive = current . source_project . clone ();
for n in 0 .. 256 {
excessive
. files
. insert ( format! ( "empty {n} .arcadia" ), String ::new ());
}
cases . push (( excessive , "project_limit" ));
for ( bundle , code ) in cases {
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source-project" )))
. header ( "if-match" , current . etag ())
. json ( & bundle )
. send ()
. await
. unwrap ();
assert_eq! ( response . status (), StatusCode ::UNPROCESSABLE_ENTITY );
let error : Value = response . json (). await . unwrap ();
let issue = & error [ "issues" ][ 0 ];
if ! code . is_empty () {
assert_eq! ( issue [ "code" ], code , "{error}" );
}
assert! ( issue [ "path" ]. is_string (), "{error}" );
assert! ( issue [ "span" ][ "start" ]. is_number (), "{error}" );
assert! ( issue [ "line" ]. as_u64 (). unwrap () >= 1 );
if code == "duplicate_declaration" {
assert_eq! ( issue [ "path" ], "team/second.arcadia" );
assert_eq! ( issue [ "related" ][ 0 ][ "path" ], "team/first.arcadia" );
}
let response = server
. client
. post ( server . url ( & format! ( "/projects/ {id} /validation" )))
. json ( & json! ({ "format" :"source-project" , "source_project" :bundle }))
. send ()
. await
. unwrap ();
assert_eq! ( response . status (), StatusCode ::OK );
let validation : Value = response . json (). await . unwrap ();
assert_eq! ( validation [ "valid" ], false , "{validation}" );
assert_eq! ( validation [ "issues" ][ 0 ][ "path" ], issue [ "path" ]);
assert_eq! ( server . get ( id ). await . source_project , current . source_project );
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), original_bytes );
}
let body = "{ \" entry \" : \" a.arcadia \" , \" files \" :{ \" a.arcadia \" : \" one \" , \" a.arcadia \" : \" two \" }}" ;
let duplicate_keys = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source-project" )))
. header ( "if-match" , current . etag ())
. header ( "content-type" , "application/json" )
. body ( body )
. send ()
. await
. unwrap ();
assert_eq! ( duplicate_keys . status (), StatusCode ::BAD_REQUEST );
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), original_bytes );
let missing_precondition = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source-project" )))
. json ( & current . source_project )
. send ()
. await
. unwrap ();
assert_eq! (
missing_precondition . status (),
StatusCode ::PRECONDITION_REQUIRED
);
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_stabilize_once_and_enforce_size_after_insertions () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let bundle = SourceBundle {
entry : "entry.arcadia" . into (),
files : BTreeMap ::from ([
(
"entry.arcadia" . into (),
"// Header rationale \n arcadia 3; project p \" P \" { include \" work.arcadia \" ; } \n "
. into (),
),
(
"work.arcadia" . into (),
"// Work rationale \n oa_activity work \" Work \" {} \n " . into (),
),
]),
};
let current = import ( & server , & bundle ). await ;
assert_ne! ( current . source_project , bundle );
assert! ( current . source_project . files [ "work.arcadia" ]. starts_with ( "// Work rationale \n " ));
assert_pair ( & current );
assert_eq! (
put ( & server , & current , & current . source_project ). await [ "changed" ],
false
);
let mut large = SourceBundle {
entry : "entry.arcadia" . into (),
files : BTreeMap ::from ([(
"entry.arcadia" . into (),
"arcadia 3; project p \" P \" {} \n // " . into (),
)]),
};
let base = serde_json ::to_vec ( & large ). unwrap (). len ();
large
. files
. get_mut ( "entry.arcadia" )
. unwrap ()
. push_str ( & "x" . repeat ( oa_server ::store ::MAX_SOURCE_PROJECT_BYTES - base - 8 ));
assert! ( serde_json ::to_vec ( & large ). unwrap (). len () < oa_server ::store ::MAX_MODEL_BYTES );
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {} /source-project" , current . resource_id )))
. header ( "if-match" , current . etag ())
. json ( & large )
. send ()
. await
. unwrap ();
assert_eq! ( response . status (), StatusCode ::PAYLOAD_TOO_LARGE );
let error : Value = response . json (). await . unwrap ();
assert_eq! ( error [ "code" ], "source_project_too_large" );
assert_eq! (
server . get ( current . resource_id ). await . source_project ,
current . source_project
);
server . stop (). await ;
let server = TestServer ::start ( directory . path (), None ). await ;
let restored = server . get ( current . resource_id ). await ;
assert_eq! ( restored . source_project , current . source_project );
assert_eq! ( restored . etag (), current . etag ());
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_concurrent_comment_and_semantic_writes_share_one_revision () {
let directory = tempfile ::tempdir (). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let original = import ( & server , & authored ()). await ;
let id = original . resource_id ;
let mut bundle = original . source_project . clone ();
bundle
. files
. get_mut ( "work.arcadia" )
. unwrap ()
. push_str ( "// Comment writer \r\n " );
let comment = server
. client
. put ( server . url ( & format! ( "/projects/ {id} /source-project" )))
. header ( "if-match" , original . etag ())
. json ( & bundle )
. send ();
let semantic = server
. client
. patch ( server . url ( & format! ( "/projects/ {id} " )))
. header ( "if-match" , original . etag ())
. json ( & json! ({ "name" :"Semantic writer" }))
. send ();
let ( comment , semantic ) = tokio ::join! ( comment , semantic );
let statuses = [ comment . unwrap (). status (), semantic . unwrap (). status ()];
assert_eq! ( statuses . iter (). filter ( | s | ** s == StatusCode ::OK ). count (), 1 );
assert_eq! (
statuses
. iter ()
. filter ( | s | ** s == StatusCode ::PRECONDITION_FAILED )
. count (),
1
);
let accepted = server . get ( id ). await ;
assert_eq! ( accepted . revision , 2 );
assert_pair ( & accepted );
if accepted . model . name == "Semantic writer" {
assert_eq! (
accepted . source_project . files [ "work.arcadia" ],
original . source_project . files [ "work.arcadia" ]
);
} else {
assert_eq! ( accepted . model , original . model );
assert_eq! ( accepted . source_project , bundle );
}
server . stop (). await ;
let server = TestServer ::start ( directory . path (), None ). await ;
assert_eq! ( server . get ( id ). await . source_project , accepted . source_project );
server . stop (). await ;
}
#[tokio::test]
async fn source_projects_upgrade_old_envelopes_without_writes_and_reject_inconsistent_pairs () {
let directory = tempfile ::tempdir (). unwrap ();
let resource = Id ::new_v4 ();
let model = graph ();
let filename = directory . path (). join ( format! ( " {resource} .json" ));
let original =
serde_json ::to_vec ( & json! ({ "resource_id" :resource , "revision" :9 , "model" :model })). unwrap ();
std ::fs ::write ( & filename , & original ). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
let current = server . get ( resource ). await ;
assert_eq! ( current . model , model );
assert_eq! ( current . revision , 9 );
assert_pair ( & current );
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), original );
let response = server
. client
. put ( server . url ( & format! ( "/projects/ {resource} /model" )))
. header ( "if-match" , current . etag ())
. json ( & model )
. send ()
. await
. unwrap ();
assert_eq! ( response . status (), StatusCode ::OK );
assert_eq! ( response . json ::< Value > (). await . unwrap ()[ "changed" ], false );
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), original );
let mut bundle = current . source_project . clone ();
bundle
. files
. get_mut ( & bundle . entry )
. unwrap ()
. push_str ( "// Reviewed source upgrade \n " );
put ( & server , & current , & bundle ). await ;
let accepted = server . get ( resource ). await ;
assert_eq! ( accepted . revision , 10 );
server . stop (). await ;
let saved = std ::fs ::read ( & filename ). unwrap ();
let mut envelope : Value = serde_json ::from_slice ( & saved ). unwrap ();
assert! ( envelope . get ( "source_project" ). is_some ());
envelope [ "model" ][ "name" ] = json! ( "Corrupted cache" );
std ::fs ::write ( & filename , serde_json ::to_vec ( & envelope ). unwrap ()). unwrap ();
assert! ( Store ::open ( directory . path ()). is_err ());
let mut envelope : Value = serde_json ::from_slice ( & saved ). unwrap ();
envelope [ "source_project" ] = Value ::Null ;
std ::fs ::write ( & filename , serde_json ::to_vec ( & envelope ). unwrap ()). unwrap ();
assert! (
Store ::open ( directory . path ()). is_err (),
"Present null source is not a legacy envelope"
);
let mut envelope : Value = serde_json ::from_slice ( & saved ). unwrap ();
let entry = envelope [ "source_project" ][ "entry" ]
. as_str ()
. unwrap ()
. to_owned ();
let header_id = format! ( " id \" {} \" " , accepted . model . id );
let source = envelope [ "source_project" ][ "files" ][ & entry ]
. as_str ()
. unwrap ();
assert! ( source . contains ( & header_id ));
envelope [ "source_project" ][ "files" ][ & entry ] = json! ( source . replacen ( & header_id , "" , 1 ));
let unstable_bytes = serde_json ::to_vec ( & envelope ). unwrap ();
std ::fs ::write ( & filename , & unstable_bytes ). unwrap ();
assert! (
Store ::open ( directory . path ()). is_err (),
"Accepted sources must not silently acquire new IDs on restart"
);
assert_eq! ( std ::fs ::read ( & filename ). unwrap (), unstable_bytes );
std ::fs ::write ( & filename , & saved ). unwrap ();
let server = TestServer ::start ( directory . path (), None ). await ;
assert_eq! ( server . get ( resource ). await . source_project , bundle );
server . stop (). await ;
}