• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MinaProtocol / mina / 1296

17 Apr 2026 09:25PM UTC coverage: 62.385% (-0.4%) from 62.744%
1296

push

buildkite

web-flow
Merge pull request #18778 from MinaProtocol/dkijania/fix-debian-upgrade-test-version

53829 of 86285 relevant lines covered (62.39%)

481369.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.59
/src/test/command_line_tests/command_line_tests.ml
1
open Core
2
open Async
3
open Mina_automation
4
open Signature_lib
5

6
module BackgroundMode = struct
7
  type t = Mina_automation_fixture.Daemon.before_bootstrap
8

9
  let test_case (test : t) =
10
    let daemon = Daemon.of_config test.config in
1✔
11
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
12
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
13
    let%bind () =
14
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
15
    in
16
    let%bind process = Daemon.start daemon in
1✔
17
    let%bind result = Daemon.wait_for_node_init process in
1✔
18
    let%bind () =
19
      match result with
20
      | Ok () ->
1✔
21
          Deferred.return ()
1✔
22
      | Error e ->
×
23
          let () = printf "Error:\n%s\n" (Error.to_string_hum e) in
×
24
          let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
×
25
          let%bind logs = Reader.file_contents log_file in
×
26
          let () = printf "Daemon logs:\n%s\n" logs in
×
27
          Writer.flushed (Lazy.force Writer.stdout)
×
28
    in
29
    let%map () = Daemon.Client.stop_daemon process.client in
1✔
30
    Mina_automation_fixture.Intf.Passed
1✔
31
end
32

33
module DaemonRecover = struct
34
  type t = Mina_automation_fixture.Daemon.before_bootstrap
35

36
  let test_case (test : t) =
37
    (let daemon = Daemon.of_config test.config in
1✔
38
     let%bind () = Daemon.Config.generate_keys test.config in
1✔
39
     let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
40
     let%bind () =
41
       Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
42
     in
43
     let%bind process = Daemon.start daemon in
1✔
44
     let%bind.Deferred.Result () = Daemon.wait_for_node_init process in
1✔
45
     let%bind.Deferred.Result _ = Daemon.Process.force_kill process in
1✔
46
     let%bind process = Daemon.start daemon in
1✔
47
     let%bind.Deferred.Result () = Daemon.wait_for_node_init process in
1✔
48
     let%map () = Daemon.Client.stop_daemon process.client in
1✔
49
     Ok () )
1✔
50
    >>| function
51
    | Ok () -> Mina_automation_fixture.Intf.Passed | Error err -> Failed err
×
52
end
53

54
let contain_log_output output =
55
  String.is_substring ~substring:"{\"timestamp\":" output
6✔
56

57
module LedgerHash = struct
58
  type t = Mina_automation_fixture.Daemon.before_bootstrap
59

60
  let test_case (test : t) =
61
    let daemon = Daemon.of_config test.config in
1✔
62
    let client = Daemon.client daemon in
1✔
63
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
64
    let%bind _ =
65
      Mina_automation_fixture.Daemon.generate_random_accounts daemon ledger_file
1✔
66
    in
67
    let%map hash = Daemon.Client.ledger_hash client ~ledger_file in
1✔
68
    if contain_log_output hash then
1✔
69
      Mina_automation_fixture.Intf.Failed
×
70
        (Error.of_string "output contains log")
×
71
    else if not (String.is_prefix ~prefix:"j" hash) then
1✔
72
      Failed (Error.of_string "invalid ledger hash prefix")
×
73
    else if Int.( <> ) (String.length hash) 52 then
1✔
74
      Failed
×
75
        (Error.createf "invalid ledger hash length (%d)" (String.length hash))
×
76
    else Passed
1✔
77
end
78

79
module LedgerCurrency = struct
80
  type t = Mina_automation_fixture.Daemon.before_bootstrap
81

82
  let test_case (test : t) =
83
    let daemon = Daemon.of_config test.config in
1✔
84
    let client = Daemon.client daemon in
1✔
85
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
86
    let%bind accounts =
87
      Mina_automation_fixture.Daemon.generate_random_accounts daemon ledger_file
1✔
88
    in
89
    let total_currency =
1✔
90
      List.map accounts ~f:(fun account ->
1✔
91
          Currency.Balance.to_nanomina_int account.balance )
10✔
92
      |> List.sum (module Int) ~f:Fn.id
93
    in
94
    let%map output = Daemon.Client.ledger_currency client ~ledger_file in
1✔
95
    let actual = Scanf.sscanf output "MINA : %f" Fn.id in
1✔
96
    let total_currency_float = float_of_int total_currency /. 1000000000.0 in
1✔
97

98
    if contain_log_output output then
99
      Mina_automation_fixture.Intf.Failed
×
100
        (Error.of_string "output contains log")
×
101
    else if not Float.(abs (total_currency_float - actual) < 0.001) then
1✔
102
      Failed
×
103
        (Error.createf "invalid mina total count %f vs %f" total_currency_float
×
104
           actual )
105
    else Passed
1✔
106
end
107

108
module ExportSnarkedLedger = struct
109
  type t = Mina_automation_fixture.Daemon.before_bootstrap
110

111
  let parse_accounts output =
112
    try
1✔
113
      let json = Yojson.Safe.from_string output in
114
      match Runtime_config.Accounts.of_yojson json with
1✔
115
      | Ok accounts ->
1✔
116
          Ok accounts
117
      | Error err ->
×
118
          Error err
119
    with exn -> Error (Exn.to_string exn)
×
120

121
  let test_case (test : t) =
122
    let daemon = Daemon.of_config test.config in
1✔
123
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
124
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
125
    let%bind () =
126
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
127
    in
128
    let%bind process = Daemon.start daemon in
1✔
129
    let%bind bootstrap_result = Daemon.wait_for_node_init process in
1✔
130
    let%bind bootstrap_ok =
131
      match bootstrap_result with
132
      | Ok () ->
1✔
133
          Deferred.return true
1✔
134
      | Error e ->
×
135
          let () = printf "Error:\n%s\n" (Error.to_string_hum e) in
×
136
          let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
×
137
          let%bind logs = Reader.file_contents log_file in
×
138
          let () = printf "Daemon logs:\n%s\n" logs in
×
139
          let%map () = Daemon.Client.stop_daemon process.client in
×
140
          false
×
141
    in
142
    if not bootstrap_ok then
1✔
143
      Deferred.return
×
144
        (Mina_automation_fixture.Intf.Failed
145
           (Error.of_string "Daemon failed to bootstrap") )
×
146
    else
147
      let%bind output =
148
        Daemon.Client.ledger_export_snarked_ledger process.client
1✔
149
      in
150
      let%map () = Daemon.Client.stop_daemon process.client in
1✔
151
      if contain_log_output output then
1✔
152
        Mina_automation_fixture.Intf.Failed
×
153
          (Error.of_string "output contains log")
×
154
      else
155
        match parse_accounts output with
1✔
156
        | Ok _ ->
1✔
157
            Passed
158
        | Error err ->
×
159
            Failed (Error.createf "invalid JSON output: %s" err)
×
160
end
161

162
module AdvancedPrintSignatureKind = struct
163
  type t = Mina_automation_fixture.Daemon.before_bootstrap
164

165
  let test_case (test : t) =
166
    let daemon = Daemon.of_config test.config in
1✔
167
    let client = Daemon.client daemon in
1✔
168
    let%map output = Daemon.Client.advanced_print_signature_kind client in
1✔
169
    let expected = "testnet" in
1✔
170

171
    if contain_log_output output then
172
      Mina_automation_fixture.Intf.Failed
×
173
        (Error.of_string "output contains log")
×
174
    else if not (String.equal expected (String.strip output)) then
1✔
175
      Failed (Error.createf "invalid signature kind %s vs %s" expected output)
×
176
    else Passed
1✔
177
end
178

179
module AdvancedCompileTimeConstants = struct
180
  type t = Mina_automation_fixture.Daemon.before_bootstrap
181

182
  let test_case (test : t) =
183
    let daemon = Daemon.of_config test.config in
1✔
184
    let client = Daemon.client daemon in
1✔
185
    let%bind config_content = Daemon.Client.test_ledger client ~n:10 in
1✔
186
    let config_content =
1✔
187
      Printf.sprintf "{ \"ledger\":{ \"accounts\":%s } }" config_content
188
    in
189
    let temp_file = Filename.temp_file "commandline" "ledger.json" in
1✔
190
    Yojson.Safe.from_string config_content |> Yojson.Safe.to_file temp_file ;
1✔
191
    let%map output =
192
      Daemon.Client.advanced_compile_time_constants client
1✔
193
        ~config_file:temp_file
194
    in
195

196
    if contain_log_output output then
1✔
197
      Mina_automation_fixture.Intf.Failed
×
198
        (Error.of_string "output contains log")
×
199
    else Passed
1✔
200
end
201

202
module AdvancedConstraintSystemDigests = struct
203
  type t = Mina_automation_fixture.Daemon.before_bootstrap
204

205
  let test_case (test : t) =
206
    let daemon = Daemon.of_config test.config in
1✔
207
    let client = Daemon.client daemon in
1✔
208
    let%map output = Daemon.Client.advanced_constraint_system_digests client in
1✔
209

210
    if contain_log_output output then
1✔
211
      Mina_automation_fixture.Intf.Failed
×
212
        (Error.of_string "output contains log")
×
213
    else Passed
1✔
214
end
215

216
(** Test the auto hard fork config generation using a single node and a small
217
    random genesis ledger. No transactions are submitted to the daemon, but it
218
    does still start up as a block producer and create blocks so that consensus
219
    will advance past the test's [slot_chain_end]. *)
220
module AutoHardforkConfigGeneration = struct
221
  type t = Mina_automation_fixture.Daemon.before_bootstrap
222

223
  let slot_tx_end = 3
224

225
  let slot_chain_end = slot_tx_end + 6
226

227
  let hard_fork_genesis_slot_delta = 1
228

229
  let slot_duration_ms = 10000
230

231
  let assert_file_exists ~path ~error_msg =
232
    let%map.Deferred exists = Sys.file_exists path in
2✔
233
    match exists with
2✔
234
    | `Yes ->
2✔
235
        Ok ()
236
    | `No | `Unknown ->
×
237
        Error (Error.of_string error_msg)
×
238

239
  let of_option opt ~error =
240
    Result.of_option opt ~error:(Error.of_string error) |> Deferred.return
4✔
241

242
  let validate_generated_config ~conf_dir ~old_genesis_timestamp =
243
    let open Deferred.Or_error.Let_syntax in
1✔
244
    (* The config is generated in a subdirectory *)
245
    let auto_fork_dir = conf_dir ^/ "auto-fork-mesa-devnet" in
246
    let daemon_json = auto_fork_dir ^/ "daemon.json" in
1✔
247
    let activated = auto_fork_dir ^/ "activated" in
1✔
248
    let%bind () =
249
      assert_file_exists ~path:daemon_json
250
        ~error_msg:"daemon.json was not generated"
251
    in
252
    let%bind () =
253
      assert_file_exists ~path:activated
254
        ~error_msg:"activated file was not created"
255
    in
256
    (* All files exist, now validate config contents *)
257
    let expected_fork_slot = slot_chain_end + hard_fork_genesis_slot_delta in
1✔
258
    (* Read and parse config using Runtime_config *)
259
    let%bind daemon_config =
260
      Yojson.Safe.from_file daemon_json
1✔
261
      |> Runtime_config.of_yojson
1✔
262
      |> Result.map_error ~f:Error.of_string
1✔
263
      |> Deferred.return
1✔
264
    in
265
    (* Extract proof.fork.global_slot_since_genesis *)
266
    let%bind proof =
267
      of_option daemon_config.proof
1✔
268
        ~error:"Generated config missing proof field"
269
    in
270
    let%bind fork =
271
      of_option proof.fork ~error:"Generated config missing proof.fork field"
1✔
272
    in
273
    let fork_slot = fork.global_slot_since_genesis in
1✔
274
    (* Verify fork slot *)
275
    let%bind () =
276
      if fork_slot <> expected_fork_slot then
277
        Deferred.Or_error.error_string
×
278
          (sprintf "proof.fork.global_slot_since_genesis is %d, expected %d"
×
279
             fork_slot expected_fork_slot )
280
      else Deferred.Or_error.return ()
1✔
281
    in
282
    (* Extract new genesis timestamp *)
283
    let%bind new_genesis =
284
      of_option daemon_config.genesis
1✔
285
        ~error:"Generated config missing genesis field"
286
    in
287
    let%bind new_genesis_timestamp =
288
      of_option new_genesis.genesis_state_timestamp
1✔
289
        ~error:"Generated config missing genesis_state_timestamp"
290
    in
291
    (* Parse timestamps and calculate expected offset *)
292
    let old_time = Time.of_string old_genesis_timestamp in
1✔
293
    let new_time = Time.of_string new_genesis_timestamp in
1✔
294
    let expected_offset_ms =
1✔
295
      Int64.( * )
296
        (Int64.of_int expected_fork_slot)
1✔
297
        (Int64.of_int slot_duration_ms)
1✔
298
    in
299
    let actual_offset_ms =
1✔
300
      Time.diff new_time old_time |> Time.Span.to_ms |> Int64.of_float
1✔
301
    in
302
    (* Verify timestamp offset *)
303
    if Int64.( <> ) actual_offset_ms expected_offset_ms then
1✔
304
      Deferred.Or_error.error_string
×
305
        (sprintf
×
306
           "Genesis timestamp offset is %Ld ms, expected %Ld ms (fork_slot=%d \
307
            * slot_duration=%d ms)"
308
           actual_offset_ms expected_offset_ms expected_fork_slot
309
           slot_duration_ms )
310
    else Deferred.Or_error.return ()
1✔
311

312
  let generate_hardfork_config daemon output =
313
    (* Generate 10 test accounts *)
314
    let client = Daemon.client daemon in
1✔
315
    let%map ledger_content = Daemon.Client.test_ledger client ~n:10 in
1✔
316
    let all_accounts =
1✔
317
      Yojson.Safe.from_string ledger_content
1✔
318
      |> Runtime_config.Accounts.of_yojson |> Result.ok_or_failwith
1✔
319
    in
320
    (* Take the first account as block producer and set its balance *)
321
    let block_producer_account, other_accounts =
1✔
322
      match all_accounts with
323
      | first :: rest ->
1✔
324
          let block_producer_balance =
325
            Currency.Balance.of_mina_int_exn 10000000
326
          in
327
          ({ first with balance = block_producer_balance }, rest)
1✔
328
      | [] ->
×
329
          failwith "No accounts generated"
330
    in
331
    (* Extract keypair from the block producer account *)
332
    let block_producer_kp =
333
      let pk_compressed =
334
        Public_key.Compressed.of_base58_check_exn block_producer_account.pk
335
      in
336
      let public_key =
1✔
337
        Public_key.decompress pk_compressed
1✔
338
        |> Option.value_exn
339
             ~message:"Failed to decompress block producer public key"
340
      in
341
      let private_key =
1✔
342
        block_producer_account.sk
343
        |> Option.value_exn
1✔
344
             ~message:"Block producer account missing private key"
345
        |> Private_key.of_base58_check_exn
346
      in
347
      { Keypair.public_key; private_key }
1✔
348
    in
349
    let accounts = block_producer_account :: other_accounts in
350
    let ledger : Runtime_config.Ledger.t =
351
      { base = Accounts accounts
352
      ; num_accounts = None
353
      ; balances = []
354
      ; hash = None
355
      ; s3_data_hash = None
356
      ; name = None
357
      ; add_genesis_winner = Some true
358
      }
359
    in
360
    let daemon : Runtime_config.Daemon.t =
361
      { txpool_max_size = None
362
      ; peer_list_url = None
363
      ; zkapp_proof_update_cost = None
364
      ; zkapp_signed_single_update_cost = None
365
      ; zkapp_signed_pair_update_cost = None
366
      ; zkapp_transaction_cost_limit = None
367
      ; max_event_elements = None
368
      ; max_action_elements = None
369
      ; zkapp_cmd_limit_hardcap = None
370
      ; slot_tx_end = Some slot_tx_end
371
      ; slot_chain_end = Some slot_chain_end
372
      ; hard_fork_genesis_slot_delta = Some hard_fork_genesis_slot_delta
373
      ; minimum_user_command_fee = None
374
      ; network_id = None
375
      ; sync_ledger_max_subtree_depth = None
376
      ; sync_ledger_default_subtree_depth = None
377
      }
378
    in
379
    let proof =
380
      Runtime_config.Proof_keys.make ~block_window_duration_ms:slot_duration_ms
381
        ()
382
    in
383
    (* Set genesis timestamp to a few minutes in the future *)
384
    let now_unix_ts = Unix.time () |> Float.to_int in
1✔
385
    let delay_minutes = 2 in
1✔
386
    let genesis_unix_ts =
387
      now_unix_ts - (now_unix_ts mod 60) + (delay_minutes * 60)
388
    in
389
    let genesis_timestamp =
390
      Time.of_span_since_epoch (Time.Span.of_int_sec genesis_unix_ts)
1✔
391
      |> Time.to_string_iso8601_basic ~zone:Time.Zone.utc
392
    in
393
    let genesis : Runtime_config.Genesis.t =
1✔
394
      { k = None
395
      ; delta = None
396
      ; slots_per_epoch = None
397
      ; slots_per_sub_window = None
398
      ; grace_period_slots = None
399
      ; genesis_state_timestamp = Some genesis_timestamp
400
      }
401
    in
402
    let runtime_config =
403
      Runtime_config.make ~ledger ~daemon ~proof ~genesis ()
404
    in
405
    Runtime_config.to_yojson runtime_config |> Yojson.Safe.to_file output ;
1✔
406
    (block_producer_kp, genesis_timestamp)
1✔
407

408
  let test_case (test : t) =
409
    let open Deferred.Let_syntax in
1✔
410
    let daemon = Daemon.of_config test.config in
411
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
412
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
413
    (* Generate config with hardfork parameters and get block producer keypair *)
414
    let%bind block_producer_kp, old_genesis_timestamp =
415
      generate_hardfork_config daemon ledger_file
1✔
416
    in
417
    (* Write block producer key to file *)
418
    let bp_key_path = test.config.dirs.conf ^/ "bp-key" in
1✔
419
    let password =
1✔
420
      lazy (Deferred.return @@ Bytes.of_string "naughty blue worm")
1✔
421
    in
422
    let%bind () =
423
      Secrets.Keypair.write_exn block_producer_kp ~privkey_path:bp_key_path
1✔
424
        ~password
425
    in
426
    (* Start daemon with migrate-exit flag and block producer key *)
427
    let%bind process =
428
      Daemon.start ~hardfork_handling:"migrate-exit"
1✔
429
        ~block_producer_key:bp_key_path daemon
430
    in
431
    (* Wait for daemon to bootstrap *)
432
    let%bind result = Daemon.wait_for_node_init process in
1✔
433
    let%bind () =
434
      match result with
435
      | Ok () ->
1✔
436
          Deferred.return ()
1✔
437
      | Error e ->
×
438
          let () = printf "Error:\n%s\n" (Error.to_string_hum e) in
×
439
          let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
×
440
          let%bind logs = Reader.file_contents log_file in
×
441
          let () = printf "Daemon logs:\n%s\n" logs in
×
442
          Writer.flushed (Lazy.force Writer.stdout)
×
443
    in
444
    (* Poll for activated file to appear (with 10 minute timeout) *)
445
    let conf_dir = test.config.dirs.conf in
1✔
446
    let auto_fork_dir = conf_dir ^/ "auto-fork-mesa-devnet" in
447
    let activated = auto_fork_dir ^/ "activated" in
1✔
448
    let start_time = Core.Time.now () in
1✔
449
    let timeout = Core.Time.Span.of_min 10. in
1✔
450
    let rec poll_for_activated () =
1✔
451
      let%bind.Deferred activated_exists = Sys.file_exists activated in
22✔
452
      match activated_exists with
22✔
453
      | `Yes ->
1✔
454
          Deferred.return `Success
455
      | `No | `Unknown ->
×
456
          if
457
            Core.Time.Span.( > )
458
              (Core.Time.diff (Core.Time.now ()) start_time)
21✔
459
              timeout
460
          then Deferred.return `Timeout
×
461
          else
462
            let%bind.Deferred () = Async.after (Core.Time.Span.of_sec 5.) in
21✔
463
            poll_for_activated ()
21✔
464
    in
465
    let%bind.Deferred result = poll_for_activated () in
1✔
466
    match result with
1✔
467
    | `Timeout ->
×
468
        Deferred.return
469
          (Mina_automation_fixture.Intf.Failed
470
             (Error.of_string "Hardfork config was not generated within timeout")
×
471
          )
472
    | `Success -> (
1✔
473
        (* Wait for daemon to auto-shutdown after generating hardfork config *)
474
        match%bind.Deferred
475
          Async.Clock.with_timeout (Core.Time.Span.of_min 5.)
1✔
476
            (Process.wait process.process)
1✔
477
        with
478
        | `Timeout ->
×
479
            Deferred.return
480
              (Mina_automation_fixture.Intf.Failed
481
                 (Error.of_string
×
482
                    "Daemon did not shut down within 5 minutes after \
483
                     generating hardfork config" ) )
484
        | `Result (Ok ()) -> (
1✔
485
            (* Daemon exited cleanly with code 0, validate generated config *)
486
            match%map.Deferred
487
              validate_generated_config ~conf_dir ~old_genesis_timestamp
488
            with
489
            | Ok () ->
1✔
490
                Mina_automation_fixture.Intf.Passed
491
            | Error err ->
×
492
                Mina_automation_fixture.Intf.Failed err )
493
        | `Result (Error (`Exit_non_zero exit_code)) ->
×
494
            Deferred.return
495
              (Mina_automation_fixture.Intf.Failed
496
                 (Error.createf "Daemon exited with non-zero status: %d"
×
497
                    exit_code ) )
498
        | `Result (Error (`Signal signal)) ->
×
499
            Deferred.return
500
              (Mina_automation_fixture.Intf.Failed
501
                 (Error.createf "Daemon terminated by signal: %s"
×
502
                    (Core.Signal.to_string signal) ) ) )
×
503
end
504

505
module HardforkStateDirMismatch = struct
506
  type t = Mina_automation_fixture.Daemon.before_bootstrap
507

508
  let test_case (test : t) =
509
    let daemon = Daemon.of_config test.config in
1✔
510
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
511
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
512
    let%bind () =
513
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
514
    in
515
    let%bind process =
516
      Daemon.start
1✔
517
        ~env:(`Extend [ ("MINA_HARDFORK_STATE_DIR", "/nonexistent") ])
518
        daemon
519
    in
520
    match%bind
521
      Async.Clock.with_timeout (Core.Time.Span.of_sec 5.)
1✔
522
        (Process.wait process.process)
1✔
523
    with
524
    | `Timeout ->
×
525
        let%map _ = Daemon.Process.force_kill process in
×
526
        Mina_automation_fixture.Intf.Failed
×
527
          (Error.of_string "Daemon did not exit within 5 seconds")
×
528
    | `Result (Ok ()) ->
×
529
        Deferred.return
530
          (Mina_automation_fixture.Intf.Failed
531
             (Error.of_string "Daemon exited with code 0, expected non-zero") )
×
532
    | `Result (Error (`Exit_non_zero _)) ->
1✔
533
        Deferred.return Mina_automation_fixture.Intf.Passed
534
    | `Result (Error (`Signal signal)) ->
×
535
        Deferred.return
536
          (Mina_automation_fixture.Intf.Failed
537
             (Error.createf "Daemon terminated by signal: %s"
×
538
                (Core.Signal.to_string signal) ) )
×
539
end
540

541
module ConfigFileOverride = struct
542
  type t = Mina_automation_fixture.Daemon.before_bootstrap
543

544
  let test_case (test : t) =
545
    let daemon = Daemon.of_config test.config in
1✔
546
    let client = Daemon.client daemon in
1✔
547
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
548
    (* Generate 10 test accounts *)
549
    let%bind ledger_content = Daemon.Client.test_ledger client ~n:10 in
1✔
550
    let accounts =
1✔
551
      Yojson.Safe.from_string ledger_content
1✔
552
      |> Runtime_config.Accounts.of_yojson |> Result.ok_or_failwith
1✔
553
    in
554
    (* Build base config: ledger + fork A + daemon with network_id *)
555
    let ledger : Runtime_config.Ledger.t =
1✔
556
      { base = Accounts accounts
557
      ; num_accounts = None
558
      ; balances = []
559
      ; hash = None
560
      ; s3_data_hash = None
561
      ; name = None
562
      ; add_genesis_winner = Some true
563
      }
564
    in
565
    let daemon_cfg : Runtime_config.Daemon.t =
566
      { txpool_max_size = None
567
      ; peer_list_url = None
568
      ; zkapp_proof_update_cost = None
569
      ; zkapp_signed_single_update_cost = None
570
      ; zkapp_signed_pair_update_cost = None
571
      ; zkapp_transaction_cost_limit = None
572
      ; max_event_elements = None
573
      ; max_action_elements = None
574
      ; zkapp_cmd_limit_hardcap = None
575
      ; slot_tx_end = None
576
      ; slot_chain_end = None
577
      ; hard_fork_genesis_slot_delta = None
578
      ; minimum_user_command_fee = None
579
      ; network_id = Some "obvious-base-config-network-id-for-test"
580
      ; sync_ledger_max_subtree_depth = None
581
      ; sync_ledger_default_subtree_depth = None
582
      }
583
    in
584
    let fork_a : Runtime_config.Fork_config.t =
585
      { state_hash = "3NKSvjaGSKiQuAt8BP1b1VCpLbJc9RcEFjYCaBYsJJFdrtd6tpaV"
586
      ; blockchain_length = 100
587
      ; global_slot_since_genesis = 200
588
      }
589
    in
590
    let proof_a = Runtime_config.Proof_keys.make ~fork:fork_a () in
591
    let base_config =
1✔
592
      Runtime_config.make ~ledger ~daemon:daemon_cfg ~proof:proof_a ()
593
    in
594
    (* Write base config as daemon.json *)
595
    let base_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
596
    Runtime_config.to_yojson base_config |> Yojson.Safe.to_file base_file ;
1✔
597
    (* Build override config: only fork B, no ledger, no daemon *)
598
    let fork_b : Runtime_config.Fork_config.t =
1✔
599
      { state_hash = "3NLRTfY4kZyJtvaP4dFenDcxfoMfT3uEpkWS913KkeXLtziyVd15"
600
      ; blockchain_length = 500
601
      ; global_slot_since_genesis = 1000
602
      }
603
    in
604
    let proof_b = Runtime_config.Proof_keys.make ~fork:fork_b () in
605
    let override_config = Runtime_config.make ~proof:proof_b () in
1✔
606
    (* Write override config *)
607
    let override_file = test.config.dirs.conf ^/ "override.json" in
1✔
608
    Runtime_config.to_yojson override_config
1✔
609
    |> Yojson.Safe.to_file override_file ;
610
    (* Start daemon with override config file *)
611
    let%bind process = Daemon.start ~config_files:[ override_file ] daemon in
1✔
612
    let%bind result = Daemon.wait_for_node_init process in
1✔
613
    let%bind () =
614
      match result with
615
      | Ok () ->
1✔
616
          Deferred.return ()
1✔
617
      | Error e ->
×
618
          let () = printf "Error:\n%s\n" (Error.to_string_hum e) in
×
619
          let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
×
620
          let%bind logs = Reader.file_contents log_file in
×
621
          let () = printf "Daemon logs:\n%s\n" logs in
×
622
          Writer.flushed (Lazy.force Writer.stdout)
×
623
    in
624
    (* Query merged runtime config from daemon *)
625
    let%bind output =
626
      Daemon.Client.advanced_runtime_config process.client
1✔
627
        ~rest_port:test.config.rest_port
628
    in
629
    let%bind () = Daemon.Client.stop_daemon process.client in
1✔
630
    (* Parse and verify the merged config *)
631
    let validate () =
1✔
632
      let of_option opt ~error =
1✔
633
        Result.of_option opt ~error:(Error.of_string error) |> Deferred.return
3✔
634
      in
635
      let open Deferred.Or_error.Let_syntax in
636
      let%bind merged_config =
637
        Yojson.Safe.from_string output
1✔
638
        |> Runtime_config.of_yojson
1✔
639
        |> Result.map_error ~f:Error.of_string
1✔
640
        |> Deferred.return
1✔
641
      in
642
      (* Verify fork B won (override) *)
643
      let%bind proof =
644
        of_option merged_config.proof ~error:"Merged config missing proof field"
1✔
645
      in
646
      let%bind fork =
647
        of_option proof.fork ~error:"Merged config missing proof.fork field"
1✔
648
      in
649
      let%bind () =
650
        if Runtime_config.Fork_config.equal fork fork_b then
651
          Deferred.Or_error.return ()
1✔
652
        else
653
          Deferred.Or_error.error_string
×
654
            "Merged proof.fork does not match expected fork B"
655
      in
656
      (* Verify daemon.network_id preserved from base *)
657
      let%bind daemon_merged =
658
        of_option merged_config.daemon
1✔
659
          ~error:"Merged config missing daemon field"
660
      in
661
      let expected_network_id = "obvious-base-config-network-id-for-test" in
1✔
662
      let%bind () =
663
        match daemon_merged.network_id with
664
        | Some id when String.equal id expected_network_id ->
1✔
665
            Deferred.Or_error.return ()
1✔
666
        | Some id ->
×
667
            Deferred.Or_error.error_string
×
668
              (sprintf "daemon.network_id is %s, expected %s" id
×
669
                 expected_network_id )
670
        | None ->
×
671
            Deferred.Or_error.error_string
×
672
              (sprintf "daemon.network_id is None, expected Some %s"
×
673
                 expected_network_id )
674
      in
675
      (* Verify ledger preserved from base *)
676
      let%bind () =
677
        match merged_config.ledger with
678
        | Some _ ->
1✔
679
            Deferred.Or_error.return ()
1✔
680
        | None ->
×
681
            Deferred.Or_error.error_string
×
682
              "Merged config missing ledger (should be preserved from base)"
683
      in
684
      Deferred.Or_error.return ()
1✔
685
    in
686
    validate ()
1✔
687
    >>| function
688
    | Ok () ->
1✔
689
        Mina_automation_fixture.Intf.Passed
690
    | Error err ->
×
691
        Mina_automation_fixture.Intf.Failed err
692
end
693

694
module PeerListUrlInvalidScheme = struct
695
  type t = Mina_automation_fixture.Daemon.before_bootstrap
696

697
  let test_case (test : t) =
698
    let daemon = Daemon.of_config test.config in
1✔
699
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
700
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
701
    let%bind () =
702
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
703
    in
704
    let%bind process =
705
      Daemon.start ~peer_list_url:"ftp://invalid-scheme.example.com/peers.txt"
1✔
706
        daemon
707
    in
708
    match%bind
709
      Async.Clock.with_timeout
1✔
710
        (Core.Time.Span.of_sec 30.)
1✔
711
        (Process.wait process.process)
1✔
712
    with
713
    | `Timeout ->
×
714
        let%bind _ = Daemon.Process.force_kill process in
×
715
        Deferred.return
×
716
          (Mina_automation_fixture.Intf.Failed
717
             (Error.of_string "Daemon did not exit within 30 seconds") )
×
718
    | `Result (Ok ()) ->
×
719
        Deferred.return
720
          (Mina_automation_fixture.Intf.Failed
721
             (Error.of_string
×
722
                "Daemon exited with code 0, expected non-zero for invalid \
723
                 peer-list-url scheme" ) )
724
    | `Result (Error (`Exit_non_zero _)) ->
1✔
725
        Deferred.return Mina_automation_fixture.Intf.Passed
726
    | `Result (Error (`Signal signal)) ->
×
727
        Deferred.return
728
          (Mina_automation_fixture.Intf.Failed
729
             (Error.of_string
×
730
                (sprintf "Daemon terminated by signal: %s"
×
731
                   (Core.Signal.to_string signal) ) ) )
×
732
end
733

734
module PeerListUrlNoScheme = struct
735
  type t = Mina_automation_fixture.Daemon.before_bootstrap
736

737
  let test_case (test : t) =
738
    let daemon = Daemon.of_config test.config in
1✔
739
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
740
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
741
    let%bind () =
742
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
743
    in
744
    let%bind process = Daemon.start ~peer_list_url:"not-a-url-at-all" daemon in
1✔
745
    match%bind
746
      Async.Clock.with_timeout
1✔
747
        (Core.Time.Span.of_sec 30.)
1✔
748
        (Process.wait process.process)
1✔
749
    with
750
    | `Timeout ->
×
751
        let%bind _ = Daemon.Process.force_kill process in
×
752
        Deferred.return
×
753
          (Mina_automation_fixture.Intf.Failed
754
             (Error.of_string "Daemon did not exit within 30 seconds") )
×
755
    | `Result (Ok ()) ->
×
756
        Deferred.return
757
          (Mina_automation_fixture.Intf.Failed
758
             (Error.of_string
×
759
                "Daemon exited with code 0, expected non-zero for \
760
                 peer-list-url without scheme" ) )
761
    | `Result (Error (`Exit_non_zero _)) ->
1✔
762
        Deferred.return Mina_automation_fixture.Intf.Passed
763
    | `Result (Error (`Signal signal)) ->
×
764
        Deferred.return
765
          (Mina_automation_fixture.Intf.Failed
766
             (Error.of_string
×
767
                (sprintf "Daemon terminated by signal: %s"
×
768
                   (Core.Signal.to_string signal) ) ) )
×
769
end
770

771
module PeerListUrlHttpWarning = struct
772
  type t = Mina_automation_fixture.Daemon.before_bootstrap
773

774
  let test_case (test : t) =
775
    let daemon = Daemon.of_config test.config in
1✔
776
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
777
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
778
    let%bind () =
779
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
780
    in
781
    let%bind process =
782
      Daemon.start
1✔
783
        ~peer_list_url:"http://bootnodes.minaprotocol.com/networks/devnet.txt"
784
        daemon
785
    in
786
    (* The daemon should not crash immediately from URL validation.
787
       Give it a few seconds to get past the peer-list-url check. *)
788
    let%bind () = after (Core.Time.Span.of_sec 10.) in
1✔
789
    (* Check if the process is still running *)
790
    let%bind process_status =
791
      Async.Clock.with_timeout (Core.Time.Span.of_sec 1.)
1✔
792
        (Process.wait process.process)
1✔
793
    in
794
    let%bind () =
795
      match process_status with
796
      | `Timeout ->
1✔
797
          (* Still running, kill it *)
798
          let%map _ = Daemon.Process.force_kill process in
1✔
799
          ()
1✔
800
      | `Result _ ->
×
801
          Deferred.return ()
×
802
    in
803
    (* Read log file and check for HTTP warning *)
804
    let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
1✔
805
    let%bind log_exists = Sys.file_exists log_file in
1✔
806
    let%map logs =
807
      match log_exists with
808
      | `Yes ->
1✔
809
          Reader.file_contents log_file
1✔
810
      | `No | `Unknown ->
×
811
          Deferred.return ""
×
812
    in
813
    if
1✔
814
      String.is_substring logs ~substring:"HTTP instead of HTTPS"
1✔
815
      || String.is_substring logs ~substring:"insecure"
×
816
    then Mina_automation_fixture.Intf.Passed
1✔
817
    else
818
      Mina_automation_fixture.Intf.Failed
×
819
        (Error.of_string
×
820
           "Expected warning about HTTP being insecure in daemon logs" )
821
end
822

823
module PeerListUrlValidHttps = struct
824
  type t = Mina_automation_fixture.Daemon.before_bootstrap
825

826
  let test_case (test : t) =
827
    let daemon = Daemon.of_config test.config in
1✔
828
    let%bind () = Daemon.Config.generate_keys test.config in
1✔
829
    let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
830
    let%bind () =
831
      Mina_automation_fixture.Daemon.generate_random_config daemon ledger_file
1✔
832
    in
833
    let%bind process =
834
      Daemon.start
1✔
835
        ~peer_list_url:"https://bootnodes.minaprotocol.com/networks/devnet.txt"
836
        daemon
837
    in
838
    (* The daemon should not crash immediately from URL validation.
839
       Give it a few seconds to get past the peer-list-url check. *)
840
    let%bind () = after (Core.Time.Span.of_sec 5.) in
1✔
841
    (* Check if the process is still running *)
842
    match%bind
843
      Async.Clock.with_timeout (Core.Time.Span.of_sec 1.)
1✔
844
        (Process.wait process.process)
1✔
845
    with
846
    | `Timeout ->
1✔
847
        (* Still running = good, the URL was accepted *)
848
        let%bind _ = Daemon.Process.force_kill process in
1✔
849
        Deferred.return Mina_automation_fixture.Intf.Passed
1✔
850
    | `Result (Ok ()) ->
×
851
        (* Exited cleanly - also fine, URL was accepted *)
852
        Deferred.return Mina_automation_fixture.Intf.Passed
853
    | `Result (Error (`Exit_non_zero exit_code)) ->
×
854
        (* Check if exit was due to URL validation by reading logs *)
855
        let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
856
        let%bind log_exists = Sys.file_exists log_file in
×
857
        let%bind logs =
858
          match log_exists with
859
          | `Yes ->
×
860
              Reader.file_contents log_file
×
861
          | `No | `Unknown ->
×
862
              Deferred.return ""
×
863
        in
864
        if
×
865
          String.is_substring logs
866
            ~substring:"peer-list-url must be a valid URL"
867
        then
868
          Deferred.return
×
869
            (Mina_automation_fixture.Intf.Failed
870
               (Error.of_string "Daemon rejected valid https peer-list-url") )
×
871
        else
872
          (* Non-zero exit for other reasons is acceptable *)
873
          Deferred.return
×
874
            (Mina_automation_fixture.Intf.Warning
875
               (sprintf "Daemon exited with code %d (not due to URL validation)"
×
876
                  exit_code ) )
877
    | `Result (Error (`Signal signal)) ->
×
878
        Deferred.return
879
          (Mina_automation_fixture.Intf.Failed
880
             (Error.of_string
×
881
                (sprintf "Daemon terminated by signal: %s"
×
882
                   (Core.Signal.to_string signal) ) ) )
×
883
end
884

885
(** Verify the daemon sends node-status reports to the configured URL.
886

887
    Starts a mock HTTP server, boots the daemon with [--node-status-url]
888
    pointing at it, waits for bootstrap, then polls the mock for collected
889
    payloads and validates expected JSON fields are present. *)
890
module NodeStatusReport = struct
891
  type t = Mina_automation_fixture.Daemon.before_bootstrap
892

893
  let default_mock_server_port = 19876
894

895
  let mock_server_port =
896
    Sys.getenv "MINA_NODE_STATUS_MOCK_PORT"
1✔
897
    |> Option.bind ~f:(fun s -> Option.try_with (fun () -> Int.of_string s))
×
898
    |> Option.value ~default:default_mock_server_port
1✔
899

900
  (** Poll [/collected-status] until at least one payload arrives. *)
901
  let poll_for_status ~port ~timeout_min =
902
    let start_time = Core.Time.now () in
1✔
903
    let timeout = Core.Time.Span.of_min timeout_min in
1✔
904
    let rec go () =
1✔
905
      let%bind statuses_result =
906
        Node_status_mock_server.collected_status ~port
907
      in
908
      match statuses_result with
20✔
909
      | Error (raw, msg) ->
×
910
          Deferred.return
911
            (Error
912
               (sprintf "Failed to parse status payload: %s\nRaw: %s" msg raw)
×
913
            )
914
      | Ok [] ->
19✔
915
          if
916
            Core.Time.Span.( > )
917
              (Core.Time.diff (Core.Time.now ()) start_time)
19✔
918
              timeout
919
          then
920
            Deferred.return (Error "Timed out waiting for node status reports")
×
921
          else
922
            let%bind () = after (Core.Time.Span.of_sec 5.) in
19✔
923
            go ()
19✔
924
      | Ok (hd :: rest) ->
1✔
925
          Deferred.return (Ok (Mina_stdlib.Nonempty_list.init hd rest))
1✔
926
    in
927
    go ()
928

929
  let test_case (test : t) =
930
    let port = mock_server_port in
1✔
931
    let mock_ref = ref None in
932
    let process_ref = ref None in
933
    Monitor.protect
934
      (fun () ->
935
        (* 1. Start mock server *)
936
        let%bind mock = Node_status_mock_server.start ~port in
937
        mock_ref := Some mock ;
1✔
938
        let%bind () = Node_status_mock_server.health_check ~port () in
1✔
939
        (* 2. Setup and start daemon *)
940
        let daemon = Daemon.of_config test.config in
1✔
941
        let%bind () = Daemon.Config.generate_keys test.config in
1✔
942
        let ledger_file = test.config.dirs.conf ^/ "daemon.json" in
1✔
943
        let%bind () =
944
          Mina_automation_fixture.Daemon.generate_random_config daemon
1✔
945
            ledger_file
946
        in
947
        let status_url = sprintf "http://localhost:%d/node-status" port in
1✔
948
        let%bind process =
949
          Daemon.start ~node_status_url:status_url ~simplified_node_stats:false
1✔
950
            daemon
951
        in
952
        process_ref := Some process ;
1✔
953
        (* 3. Wait for bootstrap *)
954
        let%bind result = Daemon.wait_for_node_init process in
1✔
955
        match result with
1✔
956
        | Error e ->
×
957
            let () = printf "Error:\n%s\n" (Error.to_string_hum e) in
×
958
            let log_file = Daemon.Config.ConfigDirs.mina_log test.config.dirs in
×
959
            let%bind logs = Reader.file_contents log_file in
×
960
            let () = printf "Daemon logs:\n%s\n" logs in
×
961
            let%bind () = Writer.flushed (Lazy.force Writer.stdout) in
×
962
            Deferred.return
×
963
              (Mina_automation_fixture.Intf.Failed
964
                 (Error.tag e ~tag:"Bootstrap failed") )
×
965
        | Ok () -> (
1✔
966
            (* 4. Poll for status reports *)
967
            let%map status_result = poll_for_status ~port ~timeout_min:3. in
968
            (* 5. Validate - if we got statuses, they're already validated by parsing *)
969
            match status_result with
1✔
970
            | Error msg ->
×
971
                Mina_automation_fixture.Intf.Failed (Error.of_string msg)
×
972
            | Ok _statuses ->
1✔
973
                Mina_automation_fixture.Intf.Passed ) )
974
      ~finally:(fun () ->
975
        let%bind () =
976
          match !process_ref with
977
          | None ->
×
978
              Deferred.unit
979
          | Some process -> (
1✔
980
              let%bind stop_result =
981
                Monitor.try_with (fun () ->
1✔
982
                    Daemon.Client.stop_daemon process.client )
1✔
983
              in
984
              match stop_result with
1✔
985
              | Ok () ->
1✔
986
                  Deferred.unit
987
              | Error _exn ->
×
988
                  (* Fall back to forcefully killing the daemon; ignore any errors *)
989
                  let%map _ =
990
                    Monitor.try_with (fun () ->
×
991
                        Daemon.Process.force_kill process )
×
992
                  in
993
                  () )
×
994
        in
995
        match !mock_ref with
1✔
996
        | None ->
×
997
            Deferred.unit
998
        | Some mock ->
1✔
999
            Node_status_mock_server.stop mock )
1000
end
1001

1002
let () =
1003
  let open Alcotest in
1004
  run "Test commadline."
×
1005
    [ ( "new-background"
1006
      , [ test_case "The mina daemon works in background mode" `Quick
1✔
1007
            (Mina_automation_runner.Runner.run_blocking
1✔
1008
               ( module Mina_automation_fixture.Daemon
1009
                        .Make_FixtureWithoutBootstrap
1010
                          (BackgroundMode) ) )
1011
        ] )
1012
    ; ( "restart"
1013
      , [ test_case "The mina daemon recovers from crash" `Quick
1✔
1014
            (Mina_automation_runner.Runner.run_blocking
1✔
1015
               ( module Mina_automation_fixture.Daemon
1016
                        .Make_FixtureWithoutBootstrap
1017
                          (DaemonRecover) ) )
1018
        ] )
1019
    ; ( "ledger-hash"
1020
      , [ test_case "The mina ledger hash evaluates correctly" `Quick
1✔
1021
            (Mina_automation_runner.Runner.run_blocking
1✔
1022
               ( module Mina_automation_fixture.Daemon
1023
                        .Make_FixtureWithoutBootstrap
1024
                          (LedgerHash) ) )
1025
        ] )
1026
    ; ( "ledger-currency"
1027
      , [ test_case "The mina ledger currency evaluates correctly" `Quick
1✔
1028
            (Mina_automation_runner.Runner.run_blocking
1✔
1029
               ( module Mina_automation_fixture.Daemon
1030
                        .Make_FixtureWithoutBootstrap
1031
                          (LedgerCurrency) ) )
1032
        ] )
1033
    ; ( "ledger-export-snarked-ledger"
1034
      , [ test_case "The mina ledger export snarked-ledger outputs valid JSON"
1✔
1035
            `Quick
1036
            (Mina_automation_runner.Runner.run_blocking
1✔
1037
               ( module Mina_automation_fixture.Daemon
1038
                        .Make_FixtureWithoutBootstrap
1039
                          (ExportSnarkedLedger) ) )
1040
        ] )
1041
    ; ( "advanced-print-signature-kind"
1042
      , [ test_case "The mina cli prints correct signature kind" `Quick
1✔
1043
            (Mina_automation_runner.Runner.run_blocking
1✔
1044
               ( module Mina_automation_fixture.Daemon
1045
                        .Make_FixtureWithoutBootstrap
1046
                          (AdvancedPrintSignatureKind) ) )
1047
        ] )
1048
    ; ( "advanced-compile-time-constants"
1049
      , [ test_case
1✔
1050
            "The mina cli does not print log when printing compile time \
1051
             constants"
1052
            `Quick
1053
            (Mina_automation_runner.Runner.run_blocking
1✔
1054
               ( module Mina_automation_fixture.Daemon
1055
                        .Make_FixtureWithoutBootstrap
1056
                          (AdvancedCompileTimeConstants) ) )
1057
        ] )
1058
    ; ( "advanced-constraint-system-digests"
1059
      , [ test_case
1✔
1060
            "The mina cli does not print log when printing constrain system \
1061
             digests"
1062
            `Quick
1063
            (Mina_automation_runner.Runner.run_blocking
1✔
1064
               ( module Mina_automation_fixture.Daemon
1065
                        .Make_FixtureWithoutBootstrap
1066
                          (AdvancedConstraintSystemDigests) ) )
1067
        ] )
1068
    ; ( "auto-hardfork-config-generation"
1069
      , [ test_case
1✔
1070
            "The mina daemon automatically generates hardfork config and shuts \
1071
             down"
1072
            `Slow
1073
            (Mina_automation_runner.Runner.run_blocking
1✔
1074
               ( module Mina_automation_fixture.Daemon
1075
                        .Make_FixtureWithoutBootstrap
1076
                          (AutoHardforkConfigGeneration) ) )
1077
        ] )
1078
    ; ( "hardfork-state-dir-mismatch"
1079
      , [ test_case
1✔
1080
            "The mina daemon fails when MINA_HARDFORK_STATE_DIR mismatches \
1081
             config dir"
1082
            `Quick
1083
            (Mina_automation_runner.Runner.run_blocking
1✔
1084
               ( module Mina_automation_fixture.Daemon
1085
                        .Make_FixtureWithoutBootstrap
1086
                          (HardforkStateDirMismatch) ) )
1087
        ] )
1088
    ; ( "config-file-override"
1089
      , [ test_case "Multiple --config-file flags merge/override configs" `Slow
1✔
1090
            (Mina_automation_runner.Runner.run_blocking
1✔
1091
               ( module Mina_automation_fixture.Daemon
1092
                        .Make_FixtureWithoutBootstrap
1093
                          (ConfigFileOverride) ) )
1094
        ] )
1095
    ; ( "peer-list-url-invalid-scheme"
1096
      , [ test_case
1✔
1097
            "The mina daemon rejects --peer-list-url with invalid scheme \
1098
             (ftp://)"
1099
            `Quick
1100
            (Mina_automation_runner.Runner.run_blocking
1✔
1101
               ( module Mina_automation_fixture.Daemon
1102
                        .Make_FixtureWithoutBootstrap
1103
                          (PeerListUrlInvalidScheme) ) )
1104
        ] )
1105
    ; ( "peer-list-url-no-scheme"
1106
      , [ test_case
1✔
1107
            "The mina daemon rejects --peer-list-url without http/https scheme"
1108
            `Quick
1109
            (Mina_automation_runner.Runner.run_blocking
1✔
1110
               ( module Mina_automation_fixture.Daemon
1111
                        .Make_FixtureWithoutBootstrap
1112
                          (PeerListUrlNoScheme) ) )
1113
        ] )
1114
    ; ( "peer-list-url-valid-https"
1115
      , [ test_case
1✔
1116
            "The mina daemon accepts --peer-list-url with valid https:// URL"
1117
            `Quick
1118
            (Mina_automation_runner.Runner.run_blocking
1✔
1119
               ( module Mina_automation_fixture.Daemon
1120
                        .Make_FixtureWithoutBootstrap
1121
                          (PeerListUrlValidHttps) ) )
1122
        ] )
1123
    ; ( "peer-list-url-http-warning"
1124
      , [ test_case
1✔
1125
            "The mina daemon warns when --peer-list-url uses http:// instead \
1126
             of https://"
1127
            `Quick
1128
            (Mina_automation_runner.Runner.run_blocking
1✔
1129
               ( module Mina_automation_fixture.Daemon
1130
                        .Make_FixtureWithoutBootstrap
1131
                          (PeerListUrlHttpWarning) ) )
1132
        ] )
1133
    ; ( "node-status-report"
1134
      , [ test_case
1✔
1135
            "The mina daemon sends node status reports to configured URL" `Slow
1136
            (Mina_automation_runner.Runner.run_blocking
1✔
1137
               ( module Mina_automation_fixture.Daemon
1138
                        .Make_FixtureWithoutBootstrap
1139
                          (NodeStatusReport) ) )
1140
        ] )
1141
    ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc