Coverage for tests/endpoints/tests_models.py: 100%

432 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-15 22:43 +0000

1from unittest.mock import patch 

2 

3from canonicalwebteam.candid import CandidClient 

4from canonicalwebteam.exceptions import ( 

5 StoreApiResponseErrorList, 

6 StoreApiResourceNotFound, 

7) 

8from webapp.helpers import api_publisher_session 

9from tests.endpoints.endpoint_testing import TestModelServiceEndpoints 

10 

11candid = CandidClient(api_publisher_session) 

12 

13 

14class TestCreateModel(TestModelServiceEndpoints): 

15 @patch( 

16 "canonicalwebteam.store_api.publishergw.PublisherGW.create_store_model" 

17 ) 

18 def test_create_model(self, mock_create_store_model): 

19 mock_create_store_model.return_value = None 

20 

21 payload = {"name": "Test Model", "api_key": self.api_key} 

22 response = self.client.post("/api/store/1/models", data=payload) 

23 data = response.json 

24 

25 self.assertEqual(response.status_code, 201) 

26 self.assertTrue(data["success"]) 

27 

28 def test_create_model_with_invalid_api_key(self): 

29 payload = {"name": "Test Model", "api_key": "invalid_api_key"} 

30 response = self.client.post("/api/store/1/models", data=payload) 

31 data = response.json 

32 

33 self.assertEqual(response.status_code, 500) 

34 self.assertFalse(data["success"]) 

35 self.assertIn("Invalid API key", data["message"]) 

36 

37 def test_name_too_long(self): 

38 payload = {"name": "some_random_long_name" * 7} 

39 response = self.client.post("/api/store/1/models", data=payload) 

40 data = response.json 

41 

42 self.assertEqual(response.status_code, 500) 

43 self.assertFalse(data["success"]) 

44 self.assertEqual( 

45 data["message"], "Name is too long. Limit 128 characters" 

46 ) 

47 

48 def test_missing_name(self): 

49 payload = {"api_key": self.api_key} 

50 response = self.client.post("/api/store/1/models", data=payload) 

51 data = response.json 

52 

53 self.assertEqual(response.status_code, 500) 

54 self.assertFalse(data["success"]) 

55 self.assertEqual(data["message"], "An error occurred") 

56 

57 

58class TestGetModels(TestModelServiceEndpoints): 

59 @patch( 

60 "canonicalwebteam.store_api.publishergw.PublisherGW.get_store_models" 

61 ) 

62 def test_get_models(self, mock_get_store_models): 

63 mock_get_store_models.return_value = { 

64 "models": [{"name": "test_model"}] 

65 } 

66 

67 response = self.client.get("/api/store/1/models") 

68 data = response.json 

69 

70 self.assertEqual(response.status_code, 200) 

71 self.assertIsNotNone(data["data"]) 

72 

73 @patch( 

74 "canonicalwebteam.store_api.publishergw.PublisherGW.get_store_models" 

75 ) 

76 def test_store_has_no_models(self, mock_get_store_models): 

77 mock_get_store_models.return_value = {"models": []} 

78 

79 response = self.client.get("/api/store/2/models") 

80 data = response.json["data"] 

81 

82 success = response.json["success"] 

83 self.assertEqual(response.status_code, 200) 

84 self.assertTrue(success) 

85 self.assertEqual(data["models"], []) 

86 

87 @patch( 

88 "canonicalwebteam.store_api.publishergw.PublisherGW.get_store_models" 

89 ) 

90 def test_invalid_store_id(self, mock_get_store_models): 

91 mock_get_store_models.side_effect = StoreApiResponseErrorList( 

92 "Store not found", 

93 404, 

94 [{"message": "Store not found"}], 

95 ) 

96 

97 response = self.client.get("/api/store/3/models") 

98 data = response.json 

99 

100 self.assertEqual(response.status_code, 500) 

101 self.assertFalse(data["success"]) 

102 self.assertIn("Store not found", data["message"]) 

103 

104 @patch( 

105 "canonicalwebteam.store_api.publishergw.PublisherGW.get_store_models" 

106 ) 

107 def test_unauthorized_user(self, mock_get_store_models): 

108 mock_get_store_models.side_effect = StoreApiResponseErrorList( 

109 "unauthorized", 401, [{"message": "unauthorized"}] 

110 ) 

111 

112 response = self.client.get("/api/store/1/models") 

113 data = response.json 

114 

115 self.assertEqual(response.status_code, 500) 

116 self.assertFalse(data["success"]) 

117 self.assertIn("Store not found", data["message"]) 

118 

119 

120class TestUpdateModel(TestModelServiceEndpoints): 

121 @patch( 

122 "canonicalwebteam.store_api.publishergw.PublisherGW.update_store_model" 

123 ) 

124 def test_update_model(self, mock_update_store_model): 

125 mock_update_store_model.return_value = None 

126 

127 payload = {"api_key": self.api_key} 

128 response = self.client.patch( 

129 "/api/store/1/models/Model1", data=payload 

130 ) 

131 data = response.json 

132 

133 self.assertEqual(response.status_code, 200) 

134 self.assertTrue(data["success"]) 

135 

136 def test_update_model_with_invalid_api_key(self): 

137 payload = {"api_key": "invalid_api_key"} 

138 response = self.client.patch( 

139 "/api/store/1/models/Model1", data=payload 

140 ) 

141 data = response.json 

142 

143 self.assertEqual(response.status_code, 500) 

144 self.assertFalse(data["success"]) 

145 self.assertEqual(data["message"], "Invalid API key") 

146 

147 @patch( 

148 "canonicalwebteam.store_api.publishergw.PublisherGW.update_store_model" 

149 ) 

150 def test_model_not_found(self, mock_update_store_model): 

151 mock_update_store_model.side_effect = StoreApiResourceNotFound( 

152 "Model not found", 404, [{"message": "Model not found"}] 

153 ) 

154 

155 payload = {"api_key": self.api_key} 

156 response = self.client.patch( 

157 "/api/store/1/models/Model1", data=payload 

158 ) 

159 data = response.json 

160 

161 self.assertEqual(response.status_code, 500) 

162 self.assertFalse(data["success"]) 

163 self.assertEqual(data["message"], "Model not found") 

164 

165 

166class TestGetRemodelAllowlist(TestModelServiceEndpoints): 

167 @patch( 

168 "canonicalwebteam.store_api.publishergw.PublisherGW" 

169 + ".get_remodel_allowlist" 

170 ) 

171 def test_get_remodel_allowlist_success(self, mock_get_remodel_allowlist): 

172 mock_allowlist = { 

173 "allowlist": [ 

174 { 

175 "created-at": "2026-02-03T11:31:06Z", 

176 "created-by": "test-user-id", 

177 "description": "Test description", 

178 "from-model": "test-from-model", 

179 "from-serial": "test-from-serial", 

180 "modified-at": None, 

181 "modified-by": None, 

182 "to-model": "test-to-model", 

183 } 

184 ] 

185 } 

186 mock_get_remodel_allowlist.return_value = mock_allowlist 

187 

188 response = self.client.get("/api/store/1/models/remodel-allowlist") 

189 data = response.json 

190 

191 self.assertEqual(response.status_code, 200) 

192 self.assertTrue(data["success"]) 

193 self.assertEqual(data["data"], mock_allowlist) 

194 

195 @patch( 

196 "canonicalwebteam.store_api.publishergw.PublisherGW" 

197 + ".get_remodel_allowlist" 

198 ) 

199 def test_get_remodel_allowlist_empty(self, mock_get_remodel_allowlist): 

200 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

201 

202 response = self.client.get("/api/store/1/models/remodel-allowlist") 

203 data = response.json 

204 

205 self.assertEqual(response.status_code, 200) 

206 self.assertTrue(data["success"]) 

207 self.assertEqual(data["data"]["allowlist"], []) 

208 

209 @patch( 

210 "canonicalwebteam.store_api.publishergw.PublisherGW" 

211 + ".get_remodel_allowlist" 

212 ) 

213 def test_get_remodel_allowlist_unauthorized( 

214 self, mock_get_remodel_allowlist 

215 ): 

216 mock_get_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

217 "unauthorized", 401, [{"message": "unauthorized"}] 

218 ) 

219 

220 response = self.client.get("/api/store/1/models/remodel-allowlist") 

221 data = response.json 

222 

223 self.assertEqual(response.status_code, 500) 

224 self.assertFalse(data["success"]) 

225 self.assertEqual(data["message"], "Store not found") 

226 

227 @patch( 

228 "canonicalwebteam.store_api.publishergw.PublisherGW" 

229 + ".get_remodel_allowlist" 

230 ) 

231 def test_get_remodel_allowlist_store_not_found( 

232 self, mock_get_remodel_allowlist 

233 ): 

234 mock_get_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

235 "Store not found", 404, [{"message": "Store not found"}] 

236 ) 

237 

238 response = self.client.get("/api/store/999/models/remodel-allowlist") 

239 data = response.json 

240 

241 self.assertEqual(response.status_code, 500) 

242 self.assertFalse(data["success"]) 

243 self.assertEqual(data["message"], "Store not found") 

244 

245 @patch( 

246 "canonicalwebteam.store_api.publishergw.PublisherGW" 

247 + ".get_remodel_allowlist" 

248 ) 

249 def test_get_remodel_allowlist_general_error( 

250 self, mock_get_remodel_allowlist 

251 ): 

252 mock_get_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

253 "Internal server error", 

254 500, 

255 [{"message": "Internal server error"}], 

256 ) 

257 

258 response = self.client.get("/api/store/1/models/remodel-allowlist") 

259 data = response.json 

260 

261 self.assertEqual(response.status_code, 500) 

262 self.assertFalse(data["success"]) 

263 self.assertEqual(data["message"], "Internal server error") 

264 

265 @patch( 

266 "canonicalwebteam.store_api.publishergw.PublisherGW" 

267 + ".get_remodel_allowlist" 

268 ) 

269 def test_get_remodel_allowlist_with_page_parameter( 

270 self, mock_get_remodel_allowlist 

271 ): 

272 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

273 

274 response = self.client.get( 

275 "/api/store/1/models/remodel-allowlist?page=2" 

276 ) 

277 data = response.json 

278 

279 self.assertEqual(response.status_code, 200) 

280 self.assertTrue(data["success"]) 

281 mock_get_remodel_allowlist.assert_called_once_with( 

282 mock_get_remodel_allowlist.call_args[0][0], # flask.session 

283 "1", # store_id 

284 {"cursor": "2", "from-model": None, "page-size": None}, # params 

285 ) 

286 

287 @patch( 

288 "canonicalwebteam.store_api.publishergw.PublisherGW" 

289 + ".get_remodel_allowlist" 

290 ) 

291 def test_get_remodel_allowlist_with_from_model_parameter( 

292 self, mock_get_remodel_allowlist 

293 ): 

294 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

295 

296 response = self.client.get( 

297 "/api/store/1/models/remodel-allowlist?from-model=test-model" 

298 ) 

299 data = response.json 

300 

301 self.assertEqual(response.status_code, 200) 

302 self.assertTrue(data["success"]) 

303 mock_get_remodel_allowlist.assert_called_once_with( 

304 mock_get_remodel_allowlist.call_args[0][0], # flask.session 

305 "1", # store_id 

306 {"cursor": None, "from-model": "test-model", "page-size": None}, 

307 ) 

308 

309 @patch( 

310 "canonicalwebteam.store_api.publishergw.PublisherGW" 

311 + ".get_remodel_allowlist" 

312 ) 

313 def test_get_remodel_allowlist_with_page_size_parameter( 

314 self, mock_get_remodel_allowlist 

315 ): 

316 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

317 

318 response = self.client.get( 

319 "/api/store/1/models/remodel-allowlist?page-size=50" 

320 ) 

321 data = response.json 

322 

323 self.assertEqual(response.status_code, 200) 

324 self.assertTrue(data["success"]) 

325 mock_get_remodel_allowlist.assert_called_once_with( 

326 mock_get_remodel_allowlist.call_args[0][0], # flask.session 

327 "1", # store_id 

328 {"cursor": None, "from-model": None, "page-size": "50"}, 

329 ) 

330 

331 @patch( 

332 "canonicalwebteam.store_api.publishergw.PublisherGW" 

333 + ".get_remodel_allowlist" 

334 ) 

335 def test_get_remodel_allowlist_with_multiple_parameters( 

336 self, mock_get_remodel_allowlist 

337 ): 

338 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

339 

340 response = self.client.get( 

341 "/api/store/1/models/remodel-allowlist" 

342 "?page=3&from-model=ubuntu-core&page-size=25" 

343 ) 

344 data = response.json 

345 

346 self.assertEqual(response.status_code, 200) 

347 self.assertTrue(data["success"]) 

348 mock_get_remodel_allowlist.assert_called_once_with( 

349 mock_get_remodel_allowlist.call_args[0][0], # flask.session 

350 "1", # store_id 

351 { 

352 "cursor": "3", 

353 "from-model": "ubuntu-core", 

354 "page-size": "25", 

355 }, 

356 ) 

357 

358 @patch( 

359 "canonicalwebteam.store_api.publishergw.PublisherGW" 

360 + ".get_remodel_allowlist" 

361 ) 

362 def test_get_remodel_allowlist_with_no_parameters( 

363 self, mock_get_remodel_allowlist 

364 ): 

365 mock_get_remodel_allowlist.return_value = {"allowlist": []} 

366 

367 response = self.client.get("/api/store/1/models/remodel-allowlist") 

368 data = response.json 

369 

370 self.assertEqual(response.status_code, 200) 

371 self.assertTrue(data["success"]) 

372 mock_get_remodel_allowlist.assert_called_once_with( 

373 mock_get_remodel_allowlist.call_args[0][0], # flask.session 

374 "1", # store_id 

375 {"cursor": None, "from-model": None, "page-size": None}, 

376 ) 

377 

378 

379class TestCreateRemodelAllowlist(TestModelServiceEndpoints): 

380 @patch( 

381 "canonicalwebteam.store_api.publishergw.PublisherGW" 

382 + ".create_remodel_allowlist" 

383 ) 

384 def test_create_remodel_allowlist_success( 

385 self, mock_create_remodel_allowlist 

386 ): 

387 mock_create_remodel_allowlist.return_value = None 

388 

389 payload = { 

390 "description": "Test remodel allowlist", 

391 "from-model": "test-from-model", 

392 "from-serial": "test-from-serial", 

393 "to-model": "test-to-model", 

394 } 

395 

396 response = self.client.post( 

397 "/api/store/1/models/remodel-allowlist", json=payload 

398 ) 

399 data = response.json 

400 

401 self.assertEqual(response.status_code, 201) 

402 self.assertTrue(data["success"]) 

403 mock_create_remodel_allowlist.assert_called_once() 

404 

405 @patch( 

406 "canonicalwebteam.store_api.publishergw.PublisherGW" 

407 + ".create_remodel_allowlist" 

408 ) 

409 def test_create_remodel_allowlist_store_not_found( 

410 self, mock_create_remodel_allowlist 

411 ): 

412 mock_create_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

413 "Store not found", 404, [{"message": "Store not found"}] 

414 ) 

415 

416 payload = { 

417 "description": "Test remodel allowlist", 

418 "from-model": "test-from-model", 

419 "to-model": "test-to-model", 

420 } 

421 

422 response = self.client.post( 

423 "/api/store/999/models/remodel-allowlist", json=payload 

424 ) 

425 data = response.json 

426 

427 self.assertEqual(response.status_code, 404) 

428 self.assertFalse(data["success"]) 

429 self.assertEqual(data["message"], "Store not found") 

430 

431 @patch( 

432 "canonicalwebteam.store_api.publishergw.PublisherGW" 

433 + ".create_remodel_allowlist" 

434 ) 

435 def test_create_remodel_allowlist_general_error( 

436 self, mock_create_remodel_allowlist 

437 ): 

438 mock_create_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

439 "Internal server error", 

440 500, 

441 [{"message": "An error occurred"}], 

442 ) 

443 

444 payload = { 

445 "description": "Test remodel allowlist", 

446 "from-model": "test-from-model", 

447 "to-model": "test-to-model", 

448 } 

449 

450 response = self.client.post( 

451 "/api/store/999/models/remodel-allowlist", json=payload 

452 ) 

453 data = response.json 

454 

455 self.assertEqual(response.status_code, 500) 

456 self.assertFalse(data["success"]) 

457 self.assertEqual(data["message"], "An error occurred") 

458 

459 

460class TestUpdateRemodelAllowlist(TestModelServiceEndpoints): 

461 @patch( 

462 "canonicalwebteam.store_api.publishergw.PublisherGW" 

463 + ".update_remodel_allowlist" 

464 ) 

465 def test_update_remodel_allowlist_success( 

466 self, mock_update_remodel_allowlist 

467 ): 

468 mock_update_remodel_allowlist.return_value = None 

469 

470 payload = { 

471 "description": "Updated remodel allowlist", 

472 "from-model": "updated-from-model", 

473 "from-serial": "updated-from-serial", 

474 "to-model": "updated-to-model", 

475 } 

476 

477 response = self.client.patch( 

478 "/api/store/1/models/remodel-allowlist", json=payload 

479 ) 

480 data = response.json 

481 

482 self.assertEqual(response.status_code, 200) 

483 self.assertTrue(data["success"]) 

484 # Verify dict payload was wrapped in a list 

485 mock_update_remodel_allowlist.assert_called_once_with( 

486 mock_update_remodel_allowlist.call_args[0][0], # flask.session 

487 "1", # store_id 

488 [payload], # Dict should be wrapped in a list 

489 ) 

490 

491 @patch( 

492 "canonicalwebteam.store_api.publishergw.PublisherGW" 

493 + ".update_remodel_allowlist" 

494 ) 

495 def test_update_remodel_allowlist_store_not_found( 

496 self, mock_update_remodel_allowlist 

497 ): 

498 mock_update_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

499 "Store not found", 404, [{"message": "Store not found"}] 

500 ) 

501 

502 payload = { 

503 "description": "Updated remodel allowlist", 

504 "from-model": "updated-from-model", 

505 "to-model": "updated-to-model", 

506 } 

507 

508 response = self.client.patch( 

509 "/api/store/999/models/remodel-allowlist", json=payload 

510 ) 

511 data = response.json 

512 

513 self.assertEqual(response.status_code, 404) 

514 self.assertFalse(data["success"]) 

515 self.assertEqual(data["message"], "Store not found") 

516 

517 @patch( 

518 "canonicalwebteam.store_api.publishergw.PublisherGW" 

519 + ".update_remodel_allowlist" 

520 ) 

521 def test_update_remodel_allowlist_models_not_found( 

522 self, mock_update_remodel_allowlist 

523 ): 

524 mock_update_remodel_allowlist.side_effect = StoreApiResourceNotFound( 

525 "Remodel allowlist not found", 

526 404, 

527 [{"message": "Remodel allowlist not found"}], 

528 ) 

529 

530 payload = { 

531 "description": "Updated remodel allowlist", 

532 "from-model": "updated-from-model", 

533 "to-model": "updated-to-model", 

534 } 

535 

536 response = self.client.patch( 

537 "/api/store/1/models/remodel-allowlist", json=payload 

538 ) 

539 data = response.json 

540 

541 self.assertEqual(response.status_code, 404) 

542 self.assertFalse(data["success"]) 

543 self.assertEqual(data["message"], "Remodel allowlist not found") 

544 

545 @patch( 

546 "canonicalwebteam.store_api.publishergw.PublisherGW" 

547 + ".update_remodel_allowlist" 

548 ) 

549 def test_update_remodel_allowlist_api_error( 

550 self, mock_update_remodel_allowlist 

551 ): 

552 mock_update_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

553 "Internal server error", 

554 500, 

555 [{"message": "An error occurred"}], 

556 ) 

557 

558 payload = { 

559 "description": "Updated remodel allowlist", 

560 "from-model": "updated-from-model", 

561 "to-model": "updated-to-model", 

562 } 

563 

564 response = self.client.patch( 

565 "/api/store/1/models/remodel-allowlist", json=payload 

566 ) 

567 data = response.json 

568 

569 self.assertEqual(response.status_code, 500) 

570 self.assertFalse(data["success"]) 

571 self.assertEqual(data["message"], "An error occurred") 

572 

573 @patch( 

574 "canonicalwebteam.store_api.publishergw.PublisherGW" 

575 + ".update_remodel_allowlist" 

576 ) 

577 def test_update_remodel_allowlist_general_exception( 

578 self, mock_update_remodel_allowlist 

579 ): 

580 mock_update_remodel_allowlist.side_effect = Exception( 

581 "Unexpected error" 

582 ) 

583 

584 payload = { 

585 "description": "Updated remodel allowlist", 

586 "from-model": "updated-from-model", 

587 "to-model": "updated-to-model", 

588 "from-serial": "test-serial", 

589 } 

590 

591 response = self.client.patch( 

592 "/api/store/1/models/remodel-allowlist", json=payload 

593 ) 

594 data = response.json 

595 

596 self.assertEqual(response.status_code, 500) 

597 self.assertFalse(data["success"]) 

598 self.assertEqual(data["message"], "An error occurred") 

599 

600 def test_update_remodel_allowlist_missing_json_payload(self): 

601 """Test update remodel allowlist with missing JSON payload.""" 

602 response = self.client.patch( 

603 "/api/store/1/models/remodel-allowlist", 

604 headers={"Content-Type": "application/json"}, 

605 data="", 

606 ) 

607 data = response.json 

608 

609 self.assertEqual(response.status_code, 400) 

610 self.assertFalse(data["success"]) 

611 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

612 

613 def test_update_remodel_allowlist_invalid_json_payload(self): 

614 """Test update remodel allowlist with invalid JSON payload.""" 

615 response = self.client.patch( 

616 "/api/store/1/models/remodel-allowlist", 

617 headers={"Content-Type": "application/json"}, 

618 data="{invalid json}", 

619 ) 

620 data = response.json 

621 

622 self.assertEqual(response.status_code, 400) 

623 self.assertFalse(data["success"]) 

624 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

625 

626 def test_update_remodel_allowlist_no_content_type(self): 

627 """Test update remodel allowlist without content-type header.""" 

628 response = self.client.patch( 

629 "/api/store/1/models/remodel-allowlist", data='{"test": "data"}' 

630 ) 

631 data = response.json 

632 

633 self.assertEqual(response.status_code, 400) 

634 self.assertFalse(data["success"]) 

635 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

636 

637 @patch( 

638 "canonicalwebteam.store_api.publishergw.PublisherGW" 

639 + ".update_remodel_allowlist" 

640 ) 

641 def test_update_remodel_allowlist_with_list_payload( 

642 self, mock_update_remodel_allowlist 

643 ): 

644 mock_update_remodel_allowlist.return_value = None 

645 

646 payload = [ 

647 { 

648 "description": "Updated remodel allowlist 1", 

649 "from-model": "updated-from-model-1", 

650 "from-serial": "updated-from-serial-1", 

651 "to-model": "updated-to-model-1", 

652 }, 

653 { 

654 "description": "Updated remodel allowlist 2", 

655 "from-model": "updated-from-model-2", 

656 "from-serial": "updated-from-serial-2", 

657 "to-model": "updated-to-model-2", 

658 }, 

659 ] 

660 

661 response = self.client.patch( 

662 "/api/store/1/models/remodel-allowlist", json=payload 

663 ) 

664 data = response.json 

665 

666 self.assertEqual(response.status_code, 200) 

667 self.assertTrue(data["success"]) 

668 # Verify the list was passed as-is, not wrapped in another list 

669 mock_update_remodel_allowlist.assert_called_once_with( 

670 mock_update_remodel_allowlist.call_args[0][0], # flask.session 

671 "1", # store_id 

672 payload, # Should be the original list, not [payload] 

673 ) 

674 

675 

676class TestDeleteRemodelAllowlist(TestModelServiceEndpoints): 

677 @patch( 

678 "canonicalwebteam.store_api.publishergw.PublisherGW" 

679 + ".delete_remodel_allowlist" 

680 ) 

681 def test_delete_remodel_allowlist_success( 

682 self, mock_delete_remodel_allowlist 

683 ): 

684 mock_delete_remodel_allowlist.return_value = None 

685 

686 payload = { 

687 "from-model": "test-from-model", 

688 "from-serial": "test-from-serial", 

689 "to-model": "test-to-model", 

690 } 

691 

692 response = self.client.delete( 

693 "/api/store/1/models/remodel-allowlist", json=payload 

694 ) 

695 data = response.json 

696 

697 self.assertEqual(response.status_code, 200) 

698 self.assertTrue(data["success"]) 

699 # Verify dict payload was wrapped in a list 

700 mock_delete_remodel_allowlist.assert_called_once_with( 

701 mock_delete_remodel_allowlist.call_args[0][0], # flask.session 

702 "1", # store_id 

703 [payload], # Dict should be wrapped in a list 

704 ) 

705 

706 @patch( 

707 "canonicalwebteam.store_api.publishergw.PublisherGW" 

708 + ".delete_remodel_allowlist" 

709 ) 

710 def test_delete_remodel_allowlist_store_not_found( 

711 self, mock_delete_remodel_allowlist 

712 ): 

713 mock_delete_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

714 "Store not found", 404, [{"message": "Store not found"}] 

715 ) 

716 

717 payload = { 

718 "from-model": "test-from-model", 

719 "from-serial": "test-from-serial", 

720 "to-model": "test-to-model", 

721 } 

722 

723 response = self.client.delete( 

724 "/api/store/999/models/remodel-allowlist", json=payload 

725 ) 

726 data = response.json 

727 

728 self.assertEqual(response.status_code, 404) 

729 self.assertFalse(data["success"]) 

730 self.assertEqual(data["message"], "Store not found") 

731 

732 @patch( 

733 "canonicalwebteam.store_api.publishergw.PublisherGW" 

734 + ".delete_remodel_allowlist" 

735 ) 

736 def test_delete_remodel_allowlist_allowlist_not_found( 

737 self, mock_delete_remodel_allowlist 

738 ): 

739 mock_delete_remodel_allowlist.side_effect = StoreApiResourceNotFound( 

740 "Remodel allowlist not found", 

741 404, 

742 [{"message": "Remodel allowlist not found"}], 

743 ) 

744 

745 payload = { 

746 "from-model": "test-from-model", 

747 "from-serial": "test-from-serial", 

748 "to-model": "test-to-model", 

749 } 

750 

751 response = self.client.delete( 

752 "/api/store/1/models/remodel-allowlist", json=payload 

753 ) 

754 data = response.json 

755 

756 self.assertEqual(response.status_code, 404) 

757 self.assertFalse(data["success"]) 

758 self.assertEqual(data["message"], "Remodel allowlist not found") 

759 

760 @patch( 

761 "canonicalwebteam.store_api.publishergw.PublisherGW" 

762 + ".delete_remodel_allowlist" 

763 ) 

764 def test_delete_remodel_allowlist_api_error( 

765 self, mock_delete_remodel_allowlist 

766 ): 

767 mock_delete_remodel_allowlist.side_effect = StoreApiResponseErrorList( 

768 "Internal server error", 

769 500, 

770 [{"message": "An error occurred"}], 

771 ) 

772 

773 payload = { 

774 "from-model": "test-from-model", 

775 "from-serial": "test-from-serial", 

776 "to-model": "test-to-model", 

777 } 

778 

779 response = self.client.delete( 

780 "/api/store/1/models/remodel-allowlist", json=payload 

781 ) 

782 data = response.json 

783 

784 self.assertEqual(response.status_code, 500) 

785 self.assertFalse(data["success"]) 

786 self.assertEqual(data["message"], "An error occurred") 

787 

788 @patch( 

789 "canonicalwebteam.store_api.publishergw.PublisherGW" 

790 + ".delete_remodel_allowlist" 

791 ) 

792 def test_delete_remodel_allowlist_general_exception( 

793 self, mock_delete_remodel_allowlist 

794 ): 

795 mock_delete_remodel_allowlist.side_effect = Exception( 

796 "Unexpected error" 

797 ) 

798 

799 payload = { 

800 "from-model": "test-from-model", 

801 "from-serial": "test-serial", 

802 "to-model": "test-to-model", 

803 } 

804 

805 response = self.client.delete( 

806 "/api/store/1/models/remodel-allowlist", json=payload 

807 ) 

808 data = response.json 

809 

810 self.assertEqual(response.status_code, 500) 

811 self.assertFalse(data["success"]) 

812 self.assertEqual(data["message"], "An error occurred") 

813 

814 def test_delete_remodel_allowlist_missing_json_payload(self): 

815 """Test delete remodel allowlist with missing JSON payload.""" 

816 response = self.client.delete( 

817 "/api/store/1/models/remodel-allowlist", 

818 headers={"Content-Type": "application/json"}, 

819 data="", 

820 ) 

821 data = response.json 

822 

823 self.assertEqual(response.status_code, 400) 

824 self.assertFalse(data["success"]) 

825 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

826 

827 def test_delete_remodel_allowlist_invalid_json_payload(self): 

828 """Test delete remodel allowlist with invalid JSON payload.""" 

829 response = self.client.delete( 

830 "/api/store/1/models/remodel-allowlist", 

831 headers={"Content-Type": "application/json"}, 

832 data="{invalid json}", 

833 ) 

834 data = response.json 

835 

836 self.assertEqual(response.status_code, 400) 

837 self.assertFalse(data["success"]) 

838 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

839 

840 def test_delete_remodel_allowlist_no_content_type(self): 

841 """Test delete remodel allowlist without content-type header.""" 

842 response = self.client.delete( 

843 "/api/store/1/models/remodel-allowlist", data='{"test": "data"}' 

844 ) 

845 data = response.json 

846 

847 self.assertEqual(response.status_code, 400) 

848 self.assertFalse(data["success"]) 

849 self.assertEqual(data["message"], "Missing or invalid JSON payload") 

850 

851 @patch( 

852 "canonicalwebteam.store_api.publishergw.PublisherGW" 

853 + ".delete_remodel_allowlist" 

854 ) 

855 def test_delete_remodel_allowlist_with_list_payload( 

856 self, mock_delete_remodel_allowlist 

857 ): 

858 mock_delete_remodel_allowlist.return_value = None 

859 

860 payload = [ 

861 { 

862 "from-model": "test-from-model-1", 

863 "from-serial": "test-from-serial-1", 

864 "to-model": "test-to-model-1", 

865 }, 

866 { 

867 "from-model": "test-from-model-2", 

868 "from-serial": "test-from-serial-2", 

869 "to-model": "test-to-model-2", 

870 }, 

871 ] 

872 

873 response = self.client.delete( 

874 "/api/store/1/models/remodel-allowlist", json=payload 

875 ) 

876 data = response.json 

877 

878 self.assertEqual(response.status_code, 200) 

879 self.assertTrue(data["success"]) 

880 # Verify the list was passed as-is, not wrapped in another list 

881 mock_delete_remodel_allowlist.assert_called_once_with( 

882 mock_delete_remodel_allowlist.call_args[0][0], # flask.session 

883 "1", # store_id 

884 payload, # Should be the original list, not [payload] 

885 ) 

886 

887 

888class TestGetSerialLog(TestModelServiceEndpoints): 

889 @patch( 

890 "canonicalwebteam.store_api.publishergw.PublisherGW" 

891 + ".get_store_model_serial_logs" 

892 ) 

893 def test_get_serial_log_success(self, mock_get_serial_logs): 

894 mock_serial_logs = { 

895 "items": [ 

896 { 

897 "brand-id": "test-brand-id", 

898 "created-at": "2026-03-27T14:34:23.666Z", 

899 "model-name": "test-model", 

900 "serial": "test-serial", 

901 } 

902 ] 

903 } 

904 mock_get_serial_logs.return_value = mock_serial_logs 

905 

906 response = self.client.get( 

907 "/api/store/test-store-id/models/test-model/serial-log" 

908 ) 

909 data = response.json 

910 

911 self.assertEqual(response.status_code, 200) 

912 self.assertTrue(data["success"]) 

913 self.assertEqual(data["data"], mock_serial_logs) 

914 

915 @patch( 

916 "canonicalwebteam.store_api.publishergw.PublisherGW" 

917 + ".get_store_model_serial_logs" 

918 ) 

919 def test_get_serial_log_empty(self, mock_get_serial_logs): 

920 mock_get_serial_logs.return_value = {"items": []} 

921 

922 response = self.client.get( 

923 "/api/store/test-store-id/models/test-model/serial-log" 

924 ) 

925 data = response.json 

926 

927 self.assertEqual(response.status_code, 200) 

928 self.assertTrue(data["success"]) 

929 self.assertEqual(data["data"]["items"], []) 

930 

931 @patch( 

932 "canonicalwebteam.store_api.publishergw.PublisherGW" 

933 + ".get_store_model_serial_logs" 

934 ) 

935 def test_get_serial_log_unauthorized(self, mock_get_serial_logs): 

936 mock_get_serial_logs.side_effect = StoreApiResponseErrorList( 

937 "unauthorized", 401, [{"message": "unauthorized"}] 

938 ) 

939 

940 response = self.client.get( 

941 "/api/store/test-store-id/models/test-model/serial-log" 

942 ) 

943 data = response.json 

944 

945 self.assertEqual(response.status_code, 500) 

946 self.assertFalse(data["success"]) 

947 self.assertEqual(data["message"], "Store not found") 

948 

949 @patch( 

950 "canonicalwebteam.store_api.publishergw.PublisherGW" 

951 + ".get_store_model_serial_logs" 

952 ) 

953 def test_get_serial_log_store_not_found(self, mock_get_serial_logs): 

954 mock_get_serial_logs.side_effect = StoreApiResponseErrorList( 

955 "Store not found", 404, [{"message": "Store not found"}] 

956 ) 

957 

958 response = self.client.get( 

959 "/api/store/999/models/test-model/serial-log" 

960 ) 

961 data = response.json 

962 

963 self.assertEqual(response.status_code, 500) 

964 self.assertFalse(data["success"]) 

965 self.assertEqual(data["message"], "Store not found") 

966 

967 @patch( 

968 "canonicalwebteam.store_api.publishergw.PublisherGW" 

969 + ".get_store_model_serial_logs" 

970 ) 

971 def test_get_serial_log_general_error(self, mock_get_serial_logs): 

972 mock_get_serial_logs.side_effect = StoreApiResponseErrorList( 

973 "Internal server error", 

974 500, 

975 [{"message": "Internal server error"}], 

976 ) 

977 

978 response = self.client.get( 

979 "/api/store/test-store-id/models/test-model/serial-log" 

980 ) 

981 data = response.json 

982 

983 self.assertEqual(response.status_code, 500) 

984 self.assertFalse(data["success"]) 

985 self.assertEqual(data["message"], "Internal server error") 

986 

987 @patch( 

988 "canonicalwebteam.store_api.publishergw.PublisherGW" 

989 + ".get_store_model_serial_logs" 

990 ) 

991 def test_get_serial_log_with_page_parameter(self, mock_get_serial_logs): 

992 mock_get_serial_logs.return_value = {"items": []} 

993 

994 response = self.client.get( 

995 "/api/store/test-store-id/models/test-model/serial-log?" 

996 "page=page-cursor" 

997 ) 

998 data = response.json 

999 

1000 self.assertEqual(response.status_code, 200) 

1001 self.assertTrue(data["success"]) 

1002 mock_get_serial_logs.assert_called_once_with( 

1003 mock_get_serial_logs.call_args[0][0], 

1004 "test-store-id", 

1005 "test-model", 

1006 { 

1007 "cursor": "page-cursor", 

1008 "start_time": None, 

1009 "end_time": None, 

1010 "page_size": None, 

1011 }, 

1012 ) 

1013 

1014 @patch( 

1015 "canonicalwebteam.store_api.publishergw.PublisherGW" 

1016 + ".get_store_model_serial_logs" 

1017 ) 

1018 def test_get_serial_log_with_start_and_end_time_parameter( 

1019 self, mock_get_serial_logs 

1020 ): 

1021 mock_get_serial_logs.return_value = {"items": []} 

1022 

1023 response = self.client.get( 

1024 "/api/store/test-store-id/models/test-model/serial-log?" 

1025 "start-time=2026-04-01T23:59:59Z&" 

1026 "end-time=2026-04-30T23:59:59Z" 

1027 ) 

1028 data = response.json 

1029 

1030 self.assertEqual(response.status_code, 200) 

1031 self.assertTrue(data["success"]) 

1032 mock_get_serial_logs.assert_called_once_with( 

1033 mock_get_serial_logs.call_args[0][0], 

1034 "test-store-id", 

1035 "test-model", 

1036 { 

1037 "cursor": None, 

1038 "start_time": "2026-04-01T23:59:59Z", 

1039 "end_time": "2026-04-30T23:59:59Z", 

1040 "page_size": None, 

1041 }, 

1042 ) 

1043 

1044 @patch( 

1045 "canonicalwebteam.store_api.publishergw" 

1046 ".PublisherGW.get_store_model_serial_logs" 

1047 ) 

1048 def test_get_serial_log_with_page_size_parameter( 

1049 self, mock_get_serial_logs 

1050 ): 

1051 mock_get_serial_logs.return_value = {"items": []} 

1052 

1053 response = self.client.get( 

1054 "/api/store/test-store-id/models/test-model/serial-log?" 

1055 "page-size=25" 

1056 ) 

1057 data = response.json 

1058 

1059 self.assertEqual(response.status_code, 200) 

1060 self.assertTrue(data["success"]) 

1061 mock_get_serial_logs.assert_called_once_with( 

1062 mock_get_serial_logs.call_args[0][0], 

1063 "test-store-id", 

1064 "test-model", 

1065 { 

1066 "cursor": None, 

1067 "start_time": None, 

1068 "end_time": None, 

1069 "page_size": "25", 

1070 }, 

1071 ) 

1072 

1073 @patch( 

1074 "canonicalwebteam.store_api.publishergw.PublisherGW" 

1075 + ".get_store_model_serial_logs" 

1076 ) 

1077 def test_get_serial_log_with_multiple_parameters( 

1078 self, mock_get_serial_logs 

1079 ): 

1080 mock_get_serial_logs.return_value = {"items": []} 

1081 

1082 response = self.client.get( 

1083 "/api/store/test-store-id/models/test-model/serial-log" 

1084 "?page=page-cursor&start-time=2026-04-01T00:00:00Z&" 

1085 "end-time=2026-04-30T23:59:59Z&page-size=25" 

1086 ) 

1087 data = response.json 

1088 

1089 self.assertEqual(response.status_code, 200) 

1090 self.assertTrue(data["success"]) 

1091 mock_get_serial_logs.assert_called_once_with( 

1092 mock_get_serial_logs.call_args[0][0], 

1093 "test-store-id", 

1094 "test-model", 

1095 { 

1096 "cursor": "page-cursor", 

1097 "start_time": "2026-04-01T00:00:00Z", 

1098 "end_time": "2026-04-30T23:59:59Z", 

1099 "page_size": "25", 

1100 }, 

1101 ) 

1102 

1103 @patch( 

1104 "canonicalwebteam.store_api.publishergw.PublisherGW" 

1105 + ".get_store_model_serial_logs" 

1106 ) 

1107 def test_get_serial_log_with_no_parameters(self, mock_get_serial_logs): 

1108 mock_get_serial_logs.return_value = {"items": []} 

1109 

1110 response = self.client.get( 

1111 "/api/store/test-store-id/models/test-model/serial-log" 

1112 ) 

1113 data = response.json 

1114 

1115 self.assertEqual(response.status_code, 200) 

1116 self.assertTrue(data["success"]) 

1117 mock_get_serial_logs.assert_called_once_with( 

1118 mock_get_serial_logs.call_args[0][0], 

1119 "test-store-id", 

1120 "test-model", 

1121 { 

1122 "cursor": None, 

1123 "start_time": None, 

1124 "end_time": None, 

1125 "page_size": None, 

1126 }, 

1127 )