|
2 | 2 |
|
3 | 3 | import base64 |
4 | 4 | import json |
| 5 | +import logging |
5 | 6 |
|
6 | 7 | from argparse import Namespace |
7 | 8 | from pathlib import Path |
|
12 | 13 | from pykotor.cli.argparser import create_parser |
13 | 14 | from pykotor.cli.commands.find_cmd import cmd_find |
14 | 15 | from pykotor.cli.commands.get_cmd import cmd_get |
| 16 | +from pykotor.cli.commands.installation_to_json import cmd_installation_to_json |
15 | 17 | from pykotor.cli.commands.diff_installation import cmd_diff_installation |
16 | 18 | from pykotor.cli.dispatch import cli_main |
17 | 19 | from pykotor.common.language import Language |
@@ -418,6 +420,177 @@ def create_install(install_path: Path, dialog_text: str, executable_name: str) - |
418 | 420 | ) |
419 | 421 |
|
420 | 422 |
|
| 423 | +def test_to_json_all_detected_rejects_positional_input_with_install_path( |
| 424 | + tmp_path: Path, capsys: pytest.CaptureFixture[str] |
| 425 | +) -> None: |
| 426 | + """Regression: --all-detected must not run batch export when a path was given (ambiguous).""" |
| 427 | + install = tmp_path / "K1" |
| 428 | + install.mkdir() |
| 429 | + (install / "chitin.key").write_bytes(b"") |
| 430 | + (install / "swkotor.exe").write_bytes(b"") |
| 431 | + |
| 432 | + out = tmp_path / "out" |
| 433 | + assert ( |
| 434 | + cli_main(["to-json", str(install), "--all-detected", "--output", str(out)]) |
| 435 | + == 1 |
| 436 | + ) |
| 437 | + captured = capsys.readouterr() |
| 438 | + combined = captured.out + captured.err |
| 439 | + assert "cannot be combined" in combined |
| 440 | + |
| 441 | + |
| 442 | +def test_cmd_installation_to_json_all_detected_rejects_path_flag( |
| 443 | + tmp_path: Path, caplog: pytest.LogCaptureFixture |
| 444 | +) -> None: |
| 445 | + logger = RobustLogger() |
| 446 | + args = Namespace( |
| 447 | + all_detected=True, |
| 448 | + path=str(tmp_path), |
| 449 | + game=None, |
| 450 | + clean=False, |
| 451 | + output=str(tmp_path / "out"), |
| 452 | + ) |
| 453 | + with caplog.at_level(logging.ERROR): |
| 454 | + assert cmd_installation_to_json(args, logger) == 1 |
| 455 | + assert "cannot be combined" in caplog.text |
| 456 | + |
| 457 | + |
| 458 | +def test_cmd_installation_to_json_all_detected_unknown_game_errors( |
| 459 | + tmp_path: Path, caplog: pytest.LogCaptureFixture |
| 460 | +) -> None: |
| 461 | + logger = RobustLogger() |
| 462 | + args = Namespace( |
| 463 | + all_detected=True, |
| 464 | + path=None, |
| 465 | + game="not-a-game", |
| 466 | + clean=False, |
| 467 | + output=str(tmp_path / "out"), |
| 468 | + ) |
| 469 | + with caplog.at_level(logging.ERROR): |
| 470 | + assert cmd_installation_to_json(args, logger) == 1 |
| 471 | + assert "Unknown game" in caplog.text |
| 472 | + |
| 473 | + |
| 474 | +def test_cmd_installation_to_json_all_detected_no_installs_errors( |
| 475 | + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture |
| 476 | +) -> None: |
| 477 | + monkeypatch.setattr( |
| 478 | + "pykotor.cli.commands.installation_to_json.get_kotor_paths_from_default", |
| 479 | + lambda: {Game.K1: [], Game.K2: []}, |
| 480 | + ) |
| 481 | + logger = RobustLogger() |
| 482 | + args = Namespace( |
| 483 | + all_detected=True, |
| 484 | + path=None, |
| 485 | + game=None, |
| 486 | + clean=False, |
| 487 | + output=str(tmp_path / "out"), |
| 488 | + ) |
| 489 | + with caplog.at_level(logging.ERROR): |
| 490 | + assert cmd_installation_to_json(args, logger) == 1 |
| 491 | + assert "No default installations were found" in caplog.text |
| 492 | + |
| 493 | + |
| 494 | +def test_to_json_all_detected_filters_by_game_k1_only( |
| 495 | + tmp_path: Path, monkeypatch: pytest.MonkeyPatch |
| 496 | +) -> None: |
| 497 | + def create_install(install_path: Path, dialog_text: str, executable_name: str) -> None: |
| 498 | + install_path.mkdir() |
| 499 | + (install_path / "Override").mkdir() |
| 500 | + (install_path / "Modules").mkdir() |
| 501 | + (install_path / "chitin.key").write_bytes(b"") |
| 502 | + (install_path / executable_name).write_bytes(b"") |
| 503 | + |
| 504 | + tlk = TLK(Language.ENGLISH) |
| 505 | + tlk.add(dialog_text, "voice") |
| 506 | + write_tlk(tlk, install_path / "dialog.tlk", ResourceType.TLK) |
| 507 | + |
| 508 | + k1_install = tmp_path / "K1only" |
| 509 | + k2_install = tmp_path / "K2only" |
| 510 | + create_install(k1_install, "k1 dialog", "swkotor.exe") |
| 511 | + create_install(k2_install, "k2 dialog", "swkotor2.exe") |
| 512 | + |
| 513 | + monkeypatch.setattr( |
| 514 | + "pykotor.cli.commands.installation_to_json.get_kotor_paths_from_default", |
| 515 | + lambda: {Game.K1: [k1_install], Game.K2: [k2_install]}, |
| 516 | + ) |
| 517 | + |
| 518 | + output_path = tmp_path / "json-export-k1-filter" |
| 519 | + assert ( |
| 520 | + cli_main( |
| 521 | + [ |
| 522 | + "to-json", |
| 523 | + "--all-detected", |
| 524 | + "--game", |
| 525 | + "k1", |
| 526 | + "--output", |
| 527 | + str(output_path), |
| 528 | + ] |
| 529 | + ) |
| 530 | + == 0 |
| 531 | + ) |
| 532 | + |
| 533 | + assert (output_path / "k1" / "0" / "dialog.tlk.json").is_file() |
| 534 | + assert not (output_path / "k2").exists() |
| 535 | + payload = json.loads( |
| 536 | + (output_path / "k1" / "0" / "dialog.tlk.json").read_text(encoding="utf-8") |
| 537 | + ) |
| 538 | + assert payload["data"]["strings"][0]["text"] == "k1 dialog" |
| 539 | + |
| 540 | + |
| 541 | +def test_cmd_installation_to_json_all_detected_returns_2_when_any_export_has_errors( |
| 542 | + tmp_path: Path, monkeypatch: pytest.MonkeyPatch |
| 543 | +) -> None: |
| 544 | + """Batch export propagates code 2 when any installation hit non-fatal serialize errors.""" |
| 545 | + |
| 546 | + def create_install(install_path: Path, label: str) -> None: |
| 547 | + install_path.mkdir() |
| 548 | + (install_path / "Override").mkdir() |
| 549 | + (install_path / "Modules").mkdir() |
| 550 | + (install_path / "chitin.key").write_bytes(b"") |
| 551 | + (install_path / "swkotor.exe").write_bytes(b"") |
| 552 | + |
| 553 | + tlk = TLK(Language.ENGLISH) |
| 554 | + tlk.add(label, "voice") |
| 555 | + write_tlk(tlk, install_path / "dialog.tlk", ResourceType.TLK) |
| 556 | + |
| 557 | + first = tmp_path / "K1A" |
| 558 | + second = tmp_path / "K1B" |
| 559 | + create_install(first, "ok") |
| 560 | + create_install(second, "ok") |
| 561 | + |
| 562 | + monkeypatch.setattr( |
| 563 | + "pykotor.cli.commands.installation_to_json.get_kotor_paths_from_default", |
| 564 | + lambda: {Game.K1: [first, second], Game.K2: []}, |
| 565 | + ) |
| 566 | + |
| 567 | + calls: list[Path] = [] |
| 568 | + |
| 569 | + def fake_export( |
| 570 | + installation_path: Path, install_output_root: Path, logger: RobustLogger |
| 571 | + ) -> int: |
| 572 | + calls.append(installation_path) |
| 573 | + if installation_path == first: |
| 574 | + return 0 |
| 575 | + return 2 |
| 576 | + |
| 577 | + monkeypatch.setattr( |
| 578 | + "pykotor.cli.commands.installation_to_json.export_installation_to_json_tree", |
| 579 | + fake_export, |
| 580 | + ) |
| 581 | + |
| 582 | + logger = _CaptureLogger() |
| 583 | + args = Namespace( |
| 584 | + all_detected=True, |
| 585 | + path=None, |
| 586 | + game=None, |
| 587 | + clean=False, |
| 588 | + output=str(tmp_path / "batch-out"), |
| 589 | + ) |
| 590 | + assert cmd_installation_to_json(args, logger) == 2 |
| 591 | + assert calls == [first, second] |
| 592 | + |
| 593 | + |
421 | 594 | def test_to_json_roundtrips_tpc_without_base64_payload(tmp_path: Path) -> None: |
422 | 595 | input_path = tmp_path / "sample.tpc" |
423 | 596 | json_path = tmp_path / "sample.tpc.json" |
|
0 commit comments