tests/test_recorder.py
| 1 | """Tests for ReasoningRecorder.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import pytest |
| 6 | |
| 7 | from pqc_reasoning_ledger import ReasoningRecorder, StepKind |
| 8 | |
| 9 | |
| 10 | def test_record_thought_appends_thought_step( |
| 11 | sample_trace_started: ReasoningRecorder, |
| 12 | ) -> None: |
| 13 | step = sample_trace_started.record_thought("I am thinking") |
| 14 | assert step.kind == StepKind.THOUGHT |
| 15 | assert step.content == "I am thinking" |
| 16 | assert sample_trace_started.trace is not None |
| 17 | assert sample_trace_started.trace.steps[-1] is step |
| 18 | |
| 19 | |
| 20 | def test_all_convenience_methods_work( |
| 21 | sample_trace_started: ReasoningRecorder, |
| 22 | ) -> None: |
| 23 | r = sample_trace_started |
| 24 | r.record_thought("t") |
| 25 | r.record_observation("o") |
| 26 | r.record_hypothesis("h") |
| 27 | r.record_deduction("d") |
| 28 | r.record_retrieval("r") |
| 29 | r.record_tool_call("tc") |
| 30 | r.record_tool_result("tr") |
| 31 | r.record_self_critique("sc") |
| 32 | r.record_refinement("rf") |
| 33 | r.record_decision("dec") |
| 34 | assert r.trace is not None |
| 35 | kinds = [s.kind for s in r.trace.steps] |
| 36 | assert kinds == [ |
| 37 | StepKind.THOUGHT, |
| 38 | StepKind.OBSERVATION, |
| 39 | StepKind.HYPOTHESIS, |
| 40 | StepKind.DEDUCTION, |
| 41 | StepKind.RETRIEVAL, |
| 42 | StepKind.TOOL_CALL, |
| 43 | StepKind.TOOL_RESULT, |
| 44 | StepKind.SELF_CRITIQUE, |
| 45 | StepKind.REFINEMENT, |
| 46 | StepKind.DECISION, |
| 47 | ] |
| 48 | |
| 49 | |
| 50 | def test_record_auto_increments_step_number( |
| 51 | sample_trace_started: ReasoningRecorder, |
| 52 | ) -> None: |
| 53 | s1 = sample_trace_started.record_thought("one") |
| 54 | s2 = sample_trace_started.record_thought("two") |
| 55 | s3 = sample_trace_started.record_thought("three") |
| 56 | assert s1.step_number == 1 |
| 57 | assert s2.step_number == 2 |
| 58 | assert s3.step_number == 3 |
| 59 | |
| 60 | |
| 61 | def test_steps_chain_via_previous_step_hash( |
| 62 | sample_trace_started: ReasoningRecorder, |
| 63 | ) -> None: |
| 64 | s1 = sample_trace_started.record_observation("first") |
| 65 | s2 = sample_trace_started.record_deduction("second") |
| 66 | s3 = sample_trace_started.record_decision("third") |
| 67 | assert s1.previous_step_hash == "0" * 64 |
| 68 | assert s2.previous_step_hash == s1.step_hash |
| 69 | assert s3.previous_step_hash == s2.step_hash |
| 70 | |
| 71 | |
| 72 | def test_seal_produces_sealed_trace_with_merkle_root( |
| 73 | sample_trace_started: ReasoningRecorder, |
| 74 | ) -> None: |
| 75 | sample_trace_started.record_observation("a") |
| 76 | sample_trace_started.record_deduction("b") |
| 77 | sealed = sample_trace_started.seal() |
| 78 | assert sealed.step_count == 2 |
| 79 | assert len(sealed.merkle_root) == 64 |
| 80 | assert sealed.final_chain_hash == sealed.steps[-1].step_hash |
| 81 | |
| 82 | |
| 83 | def test_seal_signs_the_trace( |
| 84 | sample_trace_started: ReasoningRecorder, |
| 85 | ) -> None: |
| 86 | sample_trace_started.record_decision("x") |
| 87 | sealed = sample_trace_started.seal() |
| 88 | assert sealed.signer_did |
| 89 | assert sealed.algorithm |
| 90 | assert sealed.signature |
| 91 | assert sealed.public_key |
| 92 | assert sealed.signer_did == sample_trace_started.identity.did |
| 93 | assert ( |
| 94 | sealed.algorithm |
| 95 | == sample_trace_started.identity.signing_keypair.algorithm.value |
| 96 | ) |
| 97 | |
| 98 | |
| 99 | def test_seal_empty_trace_raises( |
| 100 | sample_trace_started: ReasoningRecorder, |
| 101 | ) -> None: |
| 102 | with pytest.raises(Exception): |
| 103 | sample_trace_started.seal() |
| 104 | |