@@ -66,6 +66,98 @@ def test_load_state_corrupt_json_returns_empty(tmp_path):
6666 assert load_state (tmp_path ) == {"segments" : {}}
6767
6868
69+ def test_load_state_rejects_non_object_segments (tmp_path ):
70+ import json
71+
72+ from docgen .wizard import WizardError , load_state
73+
74+ (tmp_path / ".docgen-state.json" ).write_text (
75+ json .dumps ({"segments" : ["01" ]}), encoding = "utf-8"
76+ )
77+ with pytest .raises (WizardError , match = r"\.docgen-state.json segments must be a JSON object, not list" ):
78+ load_state (tmp_path )
79+
80+
81+ def test_load_state_rejects_non_object_segment_row (tmp_path ):
82+ import json
83+
84+ from docgen .wizard import WizardError , load_state
85+
86+ (tmp_path / ".docgen-state.json" ).write_text (
87+ json .dumps ({"segments" : {"01" : "draft" }}), encoding = "utf-8"
88+ )
89+ with pytest .raises (
90+ WizardError ,
91+ match = r"\.docgen-state.json segments\['01'\] must be a JSON object, not str" ,
92+ ):
93+ load_state (tmp_path )
94+
95+
96+ def test_load_state_null_segments_is_empty (tmp_path ):
97+ import json
98+
99+ from docgen .wizard import load_state
100+
101+ (tmp_path / ".docgen-state.json" ).write_text (
102+ json .dumps ({"segments" : None , "extra" : 1 }), encoding = "utf-8"
103+ )
104+ assert load_state (tmp_path ) == {"segments" : {}, "extra" : 1 }
105+
106+
107+ def _wizard_client (tmp_path ):
108+ from docgen .config import Config
109+ from docgen .wizard import create_app
110+
111+ yaml_path = tmp_path / "docgen.yaml"
112+ yaml_path .write_text (
113+ "repo_root: .\n segments:\n default: ['01']\n all: ['01']\n " ,
114+ encoding = "utf-8" ,
115+ )
116+ cfg = Config .from_yaml (yaml_path )
117+ return create_app (cfg ).test_client (), cfg
118+
119+
120+ def test_api_segments_rejects_list_state_segments (tmp_path ):
121+ import json
122+
123+ client , cfg = _wizard_client (tmp_path )
124+ (cfg .base_dir / ".docgen-state.json" ).write_text (
125+ json .dumps ({"segments" : []}), encoding = "utf-8"
126+ )
127+ res = client .get ("/api/segments" )
128+ assert res .status_code == 500
129+ assert "segments must be a JSON object" in res .get_json ()["error" ]
130+
131+
132+ def test_api_state_post_rejects_list_body (tmp_path ):
133+ client , _cfg = _wizard_client (tmp_path )
134+ res = client .post ("/api/state" , json = ["not" , "an" , "object" ])
135+ assert res .status_code == 400
136+ assert res .get_json ()["error" ] == "state must be a JSON object"
137+
138+
139+ def test_api_state_post_rejects_list_segments (tmp_path ):
140+ client , cfg = _wizard_client (tmp_path )
141+ res = client .post ("/api/state" , json = {"segments" : ["01" ]})
142+ assert res .status_code == 400
143+ assert "segments must be a JSON object" in res .get_json ()["error" ]
144+ assert not (cfg .base_dir / ".docgen-state.json" ).exists ()
145+
146+
147+ def test_api_state_roundtrip_object_segments (tmp_path ):
148+ client , cfg = _wizard_client (tmp_path )
149+ payload = {"segments" : {"01" : {"status" : "ready" , "revision_notes" : "n" }}}
150+ res = client .post ("/api/state" , json = payload )
151+ assert res .status_code == 200
152+ got = client .get ("/api/state" )
153+ assert got .status_code == 200
154+ assert got .get_json ()["segments" ]["01" ]["status" ] == "ready"
155+ segs = client .get ("/api/segments" )
156+ assert segs .status_code == 200
157+ assert segs .get_json ()["segments" ][0 ]["status" ] == "ready"
158+ assert (cfg .base_dir / ".docgen-state.json" ).is_file ()
159+
160+
69161def test_api_file_rejects_prefix_escape (tmp_path ):
70162 from docgen .config import Config
71163 from docgen .wizard import create_app
0 commit comments