module remote import os fn scratch(name string) !string { dir := os.join_path(os.temp_dir(), 'koh3_remote_${os.getpid()}_${name}') os.rmdir_all(dir) or {} os.mkdir_all(dir, os.MkdirParams{})! return dir } fn test_a_missing_key_is_not_an_error() { base := scratch('missing')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! object := backend.get('repo/nothing')! assert !object.found assert object.body.len == 0 } fn test_put_if_absent_refuses_a_second_write() { base := scratch('absent')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! assert backend.put_if_absent('repo/objects/abc', 'first'.bytes())! assert !backend.put_if_absent('repo/objects/abc', 'second'.bytes())! object := backend.get('repo/objects/abc')! assert object.body == 'first'.bytes() assert object.tag == tag_of('first'.bytes()) } fn test_put_if_match_only_swaps_the_version_that_was_read() { base := scratch('match')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! backend.put_if_absent('repo/head', 'one'.bytes())! tag := backend.get('repo/head')!.tag // A stale tag loses. assert !backend.put_if_match('repo/head', 'two'.bytes(), 'a-stale-tag')! assert backend.get('repo/head')!.body == 'one'.bytes() // The current tag wins. assert backend.put_if_match('repo/head', 'two'.bytes(), tag)! assert backend.get('repo/head')!.body == 'two'.bytes() // An absent key can only be created when the caller read it as absent. assert !backend.put_if_match('repo/fresh', 'x'.bytes(), tag)! assert backend.put_if_match('repo/created', 'x'.bytes(), '')! assert backend.get('repo/created')!.found } fn test_list_walks_the_prefix() { base := scratch('list')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! backend.put_if_absent('repo/saves/aaa', 'a'.bytes())! backend.put_if_absent('repo/saves/bbb', 'b'.bytes())! backend.put_if_absent('repo/objects/ccc', 'c'.bytes())! assert backend.list('repo/saves')! == ['repo/saves/aaa', 'repo/saves/bbb'] assert backend.list('repo/nothing')!.len == 0 // A writer mid-publish, or one that died, leaves its own files behind. // They are not keys: a listing that returned them would send steal after // events and saves that do not exist. os.write_file(os.join_path(base, 'repo/saves/ddd.tmp-4242'), 'half')! os.write_file(os.join_path(base, 'repo/saves/aaa.lock'), '4242')! os.write_file(os.join_path(base, 'repo/saves/aaa.lock.4242.1790000000'), '4242')! assert backend.list('repo/saves')! == ['repo/saves/aaa', 'repo/saves/bbb'] } fn test_delete_removes_the_key() { base := scratch('delete')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! backend.put_if_absent('repo/objects/abc', 'gone'.bytes())! backend.delete('repo/objects/abc')! assert !backend.get('repo/objects/abc')!.found } fn test_check_passes_on_a_working_remote_and_leaves_nothing_behind() { base := scratch('check')! defer { os.rmdir_all(base) or {} } mut backend := new_folder(base)! check(mut backend)! assert !backend.get(probe_key)!.found // A check interrupted after its first write leaves the probe behind; the // next check must not mistake it for a remote that refuses writes. backend.put_if_absent(probe_key, 'left over'.bytes())! check(mut backend)! assert !backend.get(probe_key)!.found }