// Golden fixture tests for MemoryStore
// Each test reads a golden JSON from contract/goldens/memory/ and verifies
// that the MoonBit implementation produces the expected result.
//
// All filesystem access is cross-target via moonbitlang/x/fs +
// moonbitlang/core/env, so these tests run on --target native too.
///| Monotonic counter for unique tmp dirs across same-ms calls.
let _gm_tmp_counter : Array[Int] = [0]
///| Make a temp directory with the given prefix. Uses $TMPDIR (or /tmp).
fn ffi_mk_temp_dir(prefix : String) -> String {
let tmp = (@env.get_env_var("TMPDIR")).unwrap_or("/tmp")
_gm_tmp_counter[0] = _gm_tmp_counter[0] + 1
let suffix = @env.now().to_string() + "-" + _gm_tmp_counter[0].to_string()
let dir = tmp + "/" + prefix + suffix
let _ = try? @fs.create_dir(dir)
dir
}
///| Recursive rmdir, best effort.
fn ffi_rm_rf(path : String) -> Unit {
let _ = try? @fs.remove_dir(path)
}
///| Resolve path to a golden file. Honors MNEMO_CONTRACT_DIR; defaults
///| to `./contract` relative to the working directory.
fn ffi_golden_path(name : String) -> String {
let dir = (@env.get_env_var("MNEMO_CONTRACT_DIR")).unwrap_or("./contract")
dir + "/goldens/memory/" + name
}
///| Read the golden file as UTF-8 string; "" on any error.
fn ffi_read_golden_file(path : String) -> String {
(try? @fs.read_file_to_string(path)).unwrap_or("")
}
///| Parse a golden JSON file and return as Json
fn load_golden(name : String) -> Json {
let path = ffi_golden_path(name)
let raw = ffi_read_golden_file(path)
try {
@json.parse(raw)
} catch {
_ => Json::null()
}
}
///| Extract a string field from a Json object, or return default
fn json_get_str(
obj : Map[String, Json],
key : String,
default~ : String = ""
) -> String {
match obj.get(key) {
Some(Json::String(s)) => s
_ => default
}
}
///| Extract an int field (from Number) from a Json object, or return default
fn json_get_int(
obj : Map[String, Json],
key : String,
default~ : Int = 0
) -> Int {
match obj.get(key) {
Some(Json::Number(n, ..)) => n.to_int()
_ => default
}
}
// Golden 01: add with whitespace-only content → error_code "empty"
///|
async test "golden 01: add_trim_empty" {
let golden = load_golden("01_add_trim_empty.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let content = json_get_str(input, "content")
let dir = ffi_mk_temp_dir("mnemo-test-01-")
let store = MemoryStore::load(dir)
let result = store.add(Memory, content)
ffi_rm_rf(dir)
let expected_code = json_get_str(expected, "error_code")
match result {
AddErr(_, code) => assert_eq(code, expected_code)
AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 02: injection blocked
///|
async test "golden 02: injection_blocked" {
let golden = load_golden("02_injection_blocked.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let content = json_get_str(input, "content")
let dir = ffi_mk_temp_dir("mnemo-test-02-")
let store = MemoryStore::load(dir)
let result = store.add(Memory, content)
ffi_rm_rf(dir)
let expected_code = json_get_str(expected, "error_code")
match result {
AddErr(_, code) => assert_eq(code, expected_code)
AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 03: char budget overflow (max_chars=10)
///|
async test "golden 03: char_budget_overflow" {
let golden = load_golden("03_char_budget_overflow.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let content = json_get_str(input, "content")
let max_chars = json_get_int(input, "max_chars", default=2200)
let dir = ffi_mk_temp_dir("mnemo-test-03-")
let store = MemoryStore::load(dir, max_memory=max_chars)
let result = store.add(Memory, content)
ffi_rm_rf(dir)
let expected_code = json_get_str(expected, "error_code")
match result {
AddErr(_, code) => assert_eq(code, expected_code)
AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 04: snapshot is frozen after add (snapshot taken at load time)
///|
async test "golden 04: frozen_snapshot" {
let golden = load_golden("04_frozen_snapshot.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let content = json_get_str(input, "content")
let dir = ffi_mk_temp_dir("mnemo-test-04-")
let store = MemoryStore::load(dir)
// Capture snapshot BEFORE add (at load time, should be "")
let snap_before = store.snapshot(Memory)
let result = store.add(Memory, content)
match result {
AddOk(_, _) => ()
AddErr(msg, _) => fail("add failed: \{msg}")
}
// Snapshot should be unchanged (frozen at load time)
let snap_after = store.snapshot(Memory)
ffi_rm_rf(dir)
// The golden says snapshot_unchanged: true
let snapshot_unchanged = match expected.get("snapshot_unchanged") {
Some(Json::True) => true
_ => false
}
if snapshot_unchanged {
assert_eq(snap_before, snap_after)
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 05: replace by substring
///|
async test "golden 05: replace_substring" {
let golden = load_golden("05_replace_substring.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let dir = ffi_mk_temp_dir("mnemo-test-05-")
let store = MemoryStore::load(dir)
// Setup: add initial entries
match input.get("setup") {
Some(Json::Array(arr)) =>
for item in arr {
match item {
Json::String(s) => {
let _ = store.add(Memory, s)
}
_ => ()
}
}
_ => ()
}
let old = json_get_str(input, "old")
let new_content = json_get_str(input, "new")
let result = store.replace(Memory, old, new_content)
ffi_rm_rf(dir)
// Check op_result.ok
match expected.get("op_result") {
Some(Json::Object(op_result)) =>
match op_result.get("ok") {
Some(Json::True) =>
match result {
OpOk(_, _) => ()
OpErr(msg, _) => fail("expected OpOk, got OpErr: \{msg}")
}
_ => ()
}
_ => ()
}
// Check entries
match expected.get("entries") {
Some(Json::Array(exp_entries)) => {
let actual = store.list(Memory)
assert_eq(actual.length(), exp_entries.length())
for i, exp_item in exp_entries {
match exp_item {
Json::String(s) => assert_eq(actual[i], s)
_ => ()
}
}
}
_ => ()
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 06: remove by substring
///|
async test "golden 06: remove_substring" {
let golden = load_golden("06_remove_substring.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let dir = ffi_mk_temp_dir("mnemo-test-06-")
let store = MemoryStore::load(dir)
// Setup: add initial entries
match input.get("setup") {
Some(Json::Array(arr)) =>
for item in arr {
match item {
Json::String(s) => {
let _ = store.add(Memory, s)
}
_ => ()
}
}
_ => ()
}
let substring = json_get_str(input, "substring")
let result = store.remove(Memory, substring)
ffi_rm_rf(dir)
// Check op_result.ok
match expected.get("op_result") {
Some(Json::Object(op_result)) =>
match op_result.get("ok") {
Some(Json::True) =>
match result {
OpOk(_, _) => ()
OpErr(msg, _) => fail("expected OpOk, got OpErr: \{msg}")
}
_ => ()
}
_ => ()
}
// Check entries
match expected.get("entries") {
Some(Json::Array(exp_entries)) => {
let actual = store.list(Memory)
assert_eq(actual.length(), exp_entries.length())
for i, exp_item in exp_entries {
match exp_item {
Json::String(s) => assert_eq(actual[i], s)
_ => ()
}
}
}
_ => ()
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 07: frozen snapshot is unchanged after additional adds
///|
async test "golden 07: frozen_snapshot_across_ops" {
let golden = load_golden("07_frozen_snapshot_across_ops.json")
match golden {
Json::Object(g) => {
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let snapshot_frozen = match expected.get("snapshot_frozen_after_c") {
Some(Json::True) => true
_ => false
}
// Live replay: add "a" and "b", take snapshot, add "c", snapshot must be unchanged
let dir = ffi_mk_temp_dir("mnemo-test-07-")
let store = MemoryStore::load(dir)
let _ = store.add(Memory, "entry a")
let _ = store.add(Memory, "entry b")
let snap2 = store.snapshot(Memory)
let _ = store.add(Memory, "entry c")
let snap3 = store.snapshot(Memory)
ffi_rm_rf(dir)
assert_eq(snap2 == snap3, snapshot_frozen)
}
_ => fail("invalid golden JSON")
}
}
// Golden 08: char budget boundary — 45 + delimiter(3) + 56 = 104 > 100 → budget_exceeded
///|
async test "golden 08: char_budget_boundary" {
let golden = load_golden("08_char_budget_boundary.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let limit = json_get_int(input, "limit", default=100)
let first_len = json_get_int(input, "first_len", default=45)
let second_len = json_get_int(input, "second_len", default=56)
let first = "a".repeat(first_len)
let second = "b".repeat(second_len)
let dir = ffi_mk_temp_dir("mnemo-test-08-")
let store = MemoryStore::load(dir, max_memory=limit)
let r1 = store.add(Memory, first)
let r2 = store.add(Memory, second)
ffi_rm_rf(dir)
// first must succeed
let first_ok = match expected.get("first_ok") {
Some(Json::True) => true
_ => false
}
match r1 {
AddOk(_, _) => assert_eq(true, first_ok)
AddErr(msg, _) => fail("expected first add to succeed, got: \{msg}")
}
// second must fail with budget_exceeded
let second_error_code = json_get_str(expected, "second_error_code")
match r2 {
AddErr(_, code) => assert_eq(code, second_error_code)
AddOk(_, _) => fail("expected second add to fail with \{second_error_code}, got AddOk")
}
}
_ => fail("invalid golden JSON")
}
}
// Golden 10: remove ambiguous match — two entries both match "foo" → error ambiguous_match
///|
async test "golden 10: remove_ambiguous_match" {
let golden = load_golden("10_remove_ambiguous_match.json")
match golden {
Json::Object(g) => {
let input = match g.get("input") {
Some(Json::Object(o)) => o
_ => fail("no input")
}
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let dir = ffi_mk_temp_dir("mnemo-test-10-")
let store = MemoryStore::load(dir)
// Setup: add initial entries
match input.get("setup") {
Some(Json::Array(arr)) =>
for item in arr {
match item {
Json::String(s) => {
let _ = store.add(Memory, s)
}
_ => ()
}
}
_ => ()
}
let substring = json_get_str(input, "substring")
let result = store.remove(Memory, substring)
// Check op_result: must be error with ambiguous_match
match expected.get("op_result") {
Some(Json::Object(op_result)) =>
match op_result.get("ok") {
Some(Json::False) => {
let expected_code = json_get_str(op_result, "error_code")
match result {
OpErr(_, code) => assert_eq(code, expected_code)
OpOk(_, _) =>
fail("expected OpErr(\{expected_code}), got OpOk")
}
}
_ => ()
}
_ => ()
}
// Check entries remain unchanged
match expected.get("entries") {
Some(Json::Array(exp_entries)) => {
let actual = store.list(Memory)
assert_eq(actual.length(), exp_entries.length())
for i, exp_item in exp_entries {
match exp_item {
Json::String(s) => assert_eq(actual[i], s)
_ => ()
}
}
}
_ => ()
}
ffi_rm_rf(dir)
}
_ => fail("invalid golden JSON")
}
}
// Golden 09: exhaustive injection patterns — all 13 must be blocked
///|
async test "golden 09: injection_exhaustive" {
let golden = load_golden("09_injection_exhaustive.json")
match golden {
Json::Object(g) => {
let expected = match g.get("expected") {
Some(Json::Object(o)) => o
_ => fail("no expected")
}
let all_blocked = match expected.get("all_blocked") {
Some(Json::True) => true
_ => false
}
// The cases array carries all 13 inputs; replay each
match expected.get("cases") {
Some(Json::Array(cases)) => {
let mut all_ok = true
for c in cases {
match c {
Json::Object(obj) => {
let input_prefix = json_get_str(obj, "input_prefix")
let blocked = match obj.get("blocked") {
Some(Json::True) => true
_ => false
}
if !blocked {
all_ok = false
}
// Live check: the pattern_id-named cases must all be blocked
let dir = ffi_mk_temp_dir("mnemo-test-09-")
let store = MemoryStore::load(dir)
let r = store.add(Memory, input_prefix)
ffi_rm_rf(dir)
match r {
AddErr(_, code) =>
assert_eq(code, "injection_blocked")
AddOk(_, _) =>
fail("golden 09: expected blocked for \{input_prefix}, got AddOk")
}
}
_ => ()
}
}
assert_eq(all_ok, all_blocked)
}
_ => fail("no cases array in golden 09")
}
}
_ => fail("invalid golden JSON")
}
}