amduat-api/tests/test_amduatd_space_mounts.c
Carl Niklas Rydberg e54a9009a6 Add GET /v1/space/mounts/resolve with local mount resolution tests
Add mount-aware v2 federation cursors with remote_space_id support
2026-01-24 19:50:13 +01:00

361 lines
11 KiB
C

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include "amduatd_space_mounts.h"
#include "amduatd_fed_cursor.h"
#include "amduatd_space.h"
#include "amduatd_space_manifest.h"
#include "amduatd_store.h"
#include "amduat/asl/asl_pointer_fs.h"
#include "amduat/asl/asl_store_fs_meta.h"
#include "amduat/asl/record.h"
#include "amduat/asl/ref_text.h"
#include "amduat/hash/asl1.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int failures = 0;
static void expect(bool cond, const char *msg) {
if (!cond) {
fprintf(stderr, "FAIL: %s\n", msg);
failures++;
}
}
static char *amduatd_test_make_temp_dir(void) {
char tmpl[] = "/tmp/amduatd-space-mounts-XXXXXX";
char *dir = mkdtemp(tmpl);
size_t len;
char *copy;
if (dir == NULL) {
perror("mkdtemp");
return NULL;
}
len = strlen(dir);
copy = (char *)malloc(len + 1u);
if (copy == NULL) {
fprintf(stderr, "failed to allocate temp dir copy\n");
return NULL;
}
memcpy(copy, dir, len + 1u);
return copy;
}
static bool amduatd_make_test_ref(uint8_t fill, amduat_reference_t *out_ref) {
uint8_t digest_bytes[32];
amduat_octets_t digest;
if (out_ref == NULL) {
return false;
}
memset(digest_bytes, fill, sizeof(digest_bytes));
if (!amduat_octets_clone(amduat_octets(digest_bytes, sizeof(digest_bytes)),
&digest)) {
return false;
}
*out_ref = amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256, digest);
return true;
}
static int amduatd_test_mounts_missing(void) {
char *root = amduatd_test_make_temp_dir();
amduat_asl_store_fs_config_t cfg;
amduatd_store_ctx_t store_ctx;
amduat_asl_store_t store;
amduat_asl_pointer_store_t pointer_store;
amduatd_space_t space;
amduat_reference_t manifest_ref;
char *mounts_json = NULL;
size_t mounts_len = 0u;
amduatd_space_mounts_status_t status;
if (root == NULL) {
return 1;
}
memset(&cfg, 0, sizeof(cfg));
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
fprintf(stderr, "failed to init store root\n");
free(root);
return 1;
}
memset(&store_ctx, 0, sizeof(store_ctx));
memset(&store, 0, sizeof(store));
if (!amduatd_store_init(&store,
&cfg,
&store_ctx,
root,
AMDUATD_STORE_BACKEND_FS)) {
fprintf(stderr, "failed to init store\n");
free(root);
return 1;
}
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
fprintf(stderr, "failed to init pointer store\n");
free(root);
return 1;
}
if (!amduatd_space_init(&space, "alpha", false)) {
fprintf(stderr, "failed to init space\n");
free(root);
return 1;
}
memset(&manifest_ref, 0, sizeof(manifest_ref));
status = amduatd_space_mounts_resolve(&store,
&pointer_store,
&space,
&manifest_ref,
&mounts_json,
&mounts_len);
expect(status == AMDUATD_SPACE_MOUNTS_ERR_NOT_FOUND,
"missing manifest returns not found");
expect(mounts_json == NULL, "mounts json unset on missing");
amduat_reference_free(&manifest_ref);
free(root);
return failures == 0 ? 0 : 1;
}
static int amduatd_test_mounts_resolve(void) {
char *root = amduatd_test_make_temp_dir();
amduat_asl_store_fs_config_t cfg;
amduatd_store_ctx_t store_ctx;
amduat_asl_store_t store;
amduat_asl_pointer_store_t pointer_store;
amduatd_space_t space;
amduat_reference_t pinned_ref;
char *pinned_hex = NULL;
char payload[512];
amduat_reference_t record_ref;
amduat_octets_t pointer_name = amduat_octets(NULL, 0u);
bool swapped = false;
amduatd_fed_cursor_record_t cursor;
amduat_reference_t cursor_ref;
amduat_reference_t cursor_last_ref;
char *cursor_last_hex = NULL;
amduat_reference_t manifest_ref;
char *mounts_json = NULL;
size_t mounts_len = 0u;
amduatd_space_mounts_status_t status;
const char *m0 = NULL;
const char *m1 = NULL;
const char *m2 = NULL;
if (root == NULL) {
return 1;
}
memset(&cfg, 0, sizeof(cfg));
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
fprintf(stderr, "failed to init store root\n");
free(root);
return 1;
}
memset(&store_ctx, 0, sizeof(store_ctx));
memset(&store, 0, sizeof(store));
if (!amduatd_store_init(&store,
&cfg,
&store_ctx,
root,
AMDUATD_STORE_BACKEND_FS)) {
fprintf(stderr, "failed to init store\n");
free(root);
return 1;
}
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
fprintf(stderr, "failed to init pointer store\n");
free(root);
return 1;
}
if (!amduatd_space_init(&space, "alpha", false)) {
fprintf(stderr, "failed to init space\n");
free(root);
return 1;
}
if (!amduatd_make_test_ref(0x11, &pinned_ref)) {
fprintf(stderr, "failed to make pinned ref\n");
free(root);
return 1;
}
if (!amduat_asl_ref_encode_hex(pinned_ref, &pinned_hex)) {
fprintf(stderr, "failed to encode pinned ref\n");
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
{
int n = snprintf(
payload,
sizeof(payload),
"{"
"\"version\":1,"
"\"mounts\":["
"{\"name\":\"beta\",\"peer_key\":\"peer-2\",\"space_id\":\"zeta\","
"\"mode\":\"track\"},"
"{\"name\":\"alpha\",\"peer_key\":\"peer-1\",\"space_id\":\"zeta\","
"\"mode\":\"pinned\",\"pinned_root_ref\":\"%s\"},"
"{\"name\":\"alpha\",\"peer_key\":\"peer-1\",\"space_id\":\"beta\","
"\"mode\":\"track\"}"
"]"
"}",
pinned_hex);
if (n <= 0 || (size_t)n >= sizeof(payload)) {
fprintf(stderr, "failed to build manifest payload\n");
free(pinned_hex);
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
}
free(pinned_hex);
if (amduat_asl_record_store_put(
&store,
amduat_octets(AMDUATD_SPACE_MANIFEST_1,
strlen(AMDUATD_SPACE_MANIFEST_1)),
amduat_octets((const uint8_t *)payload, strlen(payload)),
&record_ref) != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "failed to store manifest record\n");
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
if (!amduatd_space_scope_name(&space, "manifest/head", &pointer_name)) {
fprintf(stderr, "failed to build manifest pointer name\n");
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
if (amduat_asl_pointer_cas(&pointer_store,
(const char *)pointer_name.data,
false,
NULL,
&record_ref,
&swapped) != AMDUAT_ASL_POINTER_OK ||
!swapped) {
fprintf(stderr, "failed to set manifest pointer\n");
amduat_octets_free(&pointer_name);
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
amduat_octets_free(&pointer_name);
if (!amduatd_make_test_ref(0x22, &cursor_last_ref)) {
fprintf(stderr, "failed to make cursor ref\n");
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
if (!amduat_asl_ref_encode_hex(cursor_last_ref, &cursor_last_hex)) {
fprintf(stderr, "failed to encode cursor ref\n");
amduat_reference_free(&cursor_last_ref);
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(root);
return 1;
}
amduatd_fed_cursor_record_init(&cursor);
cursor.peer_key = strdup("peer-1");
cursor.space_id = strdup("alpha");
cursor.has_logseq = true;
cursor.last_logseq = 42u;
cursor.has_record_ref = true;
cursor.last_record_ref = cursor_last_ref;
memset(&cursor_ref, 0, sizeof(cursor_ref));
if (amduatd_fed_cursor_cas_set_remote(&store,
&pointer_store,
&space,
"peer-1",
"beta",
NULL,
&cursor,
&cursor_ref) !=
AMDUATD_FED_CURSOR_OK) {
fprintf(stderr, "failed to set cursor\n");
amduatd_fed_cursor_record_free(&cursor);
amduat_reference_free(&cursor_ref);
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(cursor_last_hex);
free(root);
return 1;
}
amduatd_fed_cursor_record_free(&cursor);
amduat_reference_free(&cursor_ref);
memset(&manifest_ref, 0, sizeof(manifest_ref));
status = amduatd_space_mounts_resolve(&store,
&pointer_store,
&space,
&manifest_ref,
&mounts_json,
&mounts_len);
expect(status == AMDUATD_SPACE_MOUNTS_OK, "mounts resolve ok");
expect(amduat_reference_eq(manifest_ref, record_ref),
"resolve manifest ref matches pointer");
expect(mounts_json != NULL && mounts_len != 0u,
"resolve mounts json populated");
if (mounts_json != NULL) {
m0 = strstr(mounts_json,
"\"name\":\"alpha\",\"peer_key\":\"peer-1\",\"space_id\":\"beta\"");
m1 = strstr(mounts_json,
"\"name\":\"alpha\",\"peer_key\":\"peer-1\",\"space_id\":\"zeta\"");
m2 = strstr(mounts_json,
"\"name\":\"beta\",\"peer_key\":\"peer-2\",\"space_id\":\"zeta\"");
expect(m0 != NULL && m1 != NULL && m2 != NULL,
"mounts include all entries");
if (m0 != NULL && m1 != NULL && m2 != NULL) {
expect(m0 < m1 && m1 < m2, "mounts are in canonical order");
}
expect(strstr(mounts_json, "\"mode\":\"pinned\"") != NULL,
"pinned mode present");
expect(strstr(mounts_json, "\"pinned_root_ref\":\"") != NULL,
"pinned root ref present");
expect(strstr(mounts_json, "\"cursor_namespace\":\"v2\"") != NULL,
"cursor namespace present");
expect(strstr(mounts_json, "\"cursor_scope\":\"per-peer-per-local-space\"") != NULL,
"cursor scope present");
expect(strstr(mounts_json, "\"remote_space_id\":\"beta\"") != NULL,
"remote space id present");
expect(strstr(mounts_json, "\"pull_cursor\":{\"present\":true") != NULL,
"cursor present true");
expect(strstr(mounts_json, "\"last_logseq\":42") != NULL,
"cursor last_logseq present");
expect(strstr(mounts_json, cursor_last_hex) != NULL,
"cursor ref present");
expect(strstr(mounts_json, "\"pull_cursor\":{\"present\":false") != NULL,
"cursor present false");
}
free(mounts_json);
amduat_reference_free(&manifest_ref);
amduat_reference_free(&record_ref);
amduat_reference_free(&pinned_ref);
free(cursor_last_hex);
free(root);
return failures == 0 ? 0 : 1;
}
int main(void) {
if (amduatd_test_mounts_missing() != 0) {
return 1;
}
if (amduatd_test_mounts_resolve() != 0) {
return 1;
}
return failures == 0 ? 0 : 1;
}