1module cli23import os4import compress.zlib5import crypto.sha2566import json27import src.exchange8import src.release9import src.remote10import src.events11import src.history12import src.snapshot13import src.storage1415fn test_doctor_checks_cycles_and_missing_heads_without_walking_forever() {16 saves := [history.Save{ id: 'aaaaaaaaaaaa', parent: 'bbbbbbbbbbbb' },17 history.Save{ id: 'bbbbbbbbbbbb', parent: 'aaaaaaaaaaaa' }]18 assert doctor_history(saves, 'cccccccccccc').any(it.contains('head'))19 assert doctor_history(saves, 'aaaaaaaaaaaa').any(it.contains('cycle'))20 assert doctor_history([history.Save{ id: 'aaaaaaaaaaaa', parent: 'cccccccccccc' }],21 'aaaaaaaaaaaa').any(it.contains('missing parent'))22 assert doctor_history([]history.Save{}, '').len == 023 assert doctor_history([history.Save{ id: 'aaaaaaaaaaaa' }], '').any(it.contains('no head'))24}2526fn test_doctor_checks_off_line_paths_and_file_directory_collisions() {27 mut report := DoctorJson{}28 doctor_files('aaaaaaaaaaaa', [snapshot.FileEntry{ path: '../unsafe' },29 snapshot.FileEntry{ path: 'a' }, snapshot.FileEntry{ path: 'a/b' }], []events.Event{},30 mut report)31 assert report.problems.any(it.contains('unsafe path'))32 assert report.problems.any(it.contains('file and a directory'))33}3435fn test_doctor_reports_unsafe_save_paths_with_a_quoted_strip_command() {36 dir := os.join_path(os.temp_dir(), 'koh3_doctor_paths_${os.getpid()}')37 os.rmdir_all(dir) or {}38 os.mkdir_all(dir)!39 defer {40 os.rmdir_all(dir) or {}41 }42 store := storage.create(dir)!43 defer {44 store.close()45 }46 hash := store.put_object('unsafe'.bytes())!47 store.begin()!48 history.import_save(store, history.Save{49 id: 'badbadbadbad'50 }, [snapshot.FileEntry{51 path: "../June's file"52 hash: hash53 }])!54 history.set_head(store, 'badbadbadbad')!55 store.end()!56 mut report := DoctorJson{}57 doctor_files('badbadbadbad', history.files_of(store, 'badbadbadbad')!, []events.Event{}, mut report)58 assert report.problems == [59 "save [badbadbadbad] has unsafe path ../June's file — koh strip '../June'\\''s file'",60 ]61}6263struct DoctorReadOnly {64mut:65 items map[string][]u866}6768fn (mut backend DoctorReadOnly) get(key string) !remote.Object {69 if body := backend.items[key] {70 return remote.Object{ found: true, body: body }71 }72 return remote.Object{}73}7475fn (mut backend DoctorReadOnly) list(prefix string) ![]string {76 return backend.items.keys().filter(it.starts_with('${prefix}/'))77}7879fn (mut backend DoctorReadOnly) put_if_absent(key string, body []u8) !bool {80 panic('Doctor attempted a remote write')81}8283fn (mut backend DoctorReadOnly) put_if_match(key string, body []u8, tag string) !bool {84 panic('Doctor attempted a remote write')85}8687fn (mut backend DoctorReadOnly) delete(key string) ! {88 panic('Doctor attempted a remote delete')89}9091fn test_doctor_remote_audits_unreferenced_objects_and_release_checksums_without_writes() {92 data := 'file'.bytes()93 hash := storage.hash_hex(data)94 mut backend := DoctorReadOnly{95 items: {96 'repo/head': 'aaaaaaaaaaaa'.bytes()97 'repo/saves/aaaaaaaaaaaa': json2.encode(exchange.SaveRecord{98 id: 'aaaaaaaaaaaa'99 files: [exchange.FileRecord{ path: 'file', hash: hash }]100 }).bytes()101 'repo/objects/${hash}': zlib.compress(data)!102 'repo/releases/latest': '1.0.0'.bytes()103 'repo/releases/1.0.0/release.json': json2.encode(release.Record{104 repo: 'repo'105 version: '1.0.0'106 save: 'aaaaaaaaaaaa'107 files: [release.File{108 platform: 'linux-aarch64'109 name: 'app'110 size: data.len111 blake3: hash112 sha256: sha256.sum256(data).hex()113 }]114 }).bytes()115 'repo/releases/1.0.0/linux-aarch64/app': data116 }117 }118 mut report := DoctorJson{ head_id: 'aaaaaaaaaaaa' }119 doctor_remote_content(mut backend, 'repo', mut report)!120 assert report.problems.len == 0121 assert report.remote_saves == 1122 assert report.remote_releases == 1123 backend.items['repo/objects/${storage.hash_hex('unreferenced'.bytes())}'] = 'bad zlib'.bytes()124 backend.items['repo/releases/1.0.0/linux-aarch64/app'] = 'corrupt'.bytes()125 mut damaged := DoctorJson{}126 doctor_remote_content(mut backend, 'repo', mut damaged)!127 assert damaged.problems.any(it.contains('damaged remote object'))128 assert damaged.problems.any(it.contains('checksum verification'))129}130131fn test_doctor_reports_event_errors_strip_residue_and_flags() {132 mut report := DoctorJson{}133 saves := [history.Save{ id: 'aaaaaaaaaaaa' }]134 event_list := [events.Event{ id: 'strip', kind: 'strip', path: 'private' },135 events.Event{ id: 'flag', kind: 'flag', save_id: 'aaaaaaaaaaaa' },136 events.Event{ id: 'bad', kind: 'unknown' }]137 doctor_events(event_list, saves, mut report)138 doctor_files(saves[0].id, [snapshot.FileEntry{ path: 'private/file' }], event_list, mut report)139 assert report.flagged_saves == 1140 assert report.problems.any(it.contains('unknown kind'))141 assert report.problems.any(it.contains('stripped path'))142}