No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

112 líneas
5.1KB

  1. <?php
  2. declare(strict_types=1);
  3. use App\Controllers\CardsController;
  4. use Core\Database;
  5. use Core\Request;
  6. spl_autoload_register(static function (string $class): void {
  7. $root = str_starts_with($class, 'App\\') ? 'app' : 'core';
  8. $path = __DIR__ . '/../' . $root . '/' . str_replace('\\', '/', substr($class, strpos($class, '\\') + 1)) . '.php';
  9. if (is_file($path)) {
  10. require $path;
  11. }
  12. });
  13. function database(): Database
  14. {
  15. static $database = null;
  16. if ($database === null) {
  17. $database = new Database([
  18. 'dsn' => 'sqlite::memory:',
  19. 'options' => [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION],
  20. ]);
  21. $database->pdo()->exec('PRAGMA foreign_keys = ON');
  22. }
  23. return $database;
  24. }
  25. function check(bool $condition, string $message): void
  26. {
  27. if (!$condition) {
  28. throw new RuntimeException($message);
  29. }
  30. }
  31. $db = database();
  32. foreach ([
  33. '20260522_000001_create_boards_table.php',
  34. '20260522_000002_create_board_columns_table.php',
  35. '20260522_000003_create_swim_lanes_table.php',
  36. '20260522_000004_create_cards_table.php',
  37. '20260615_000003_add_cell_entered_at_to_cards.php',
  38. '20260917_000001_create_card_movements_table.php',
  39. ] as $migrationFile) {
  40. (require __DIR__ . '/../database/migrations/' . $migrationFile)->up($db);
  41. }
  42. $db->execute("INSERT INTO boards (id, name, slug) VALUES (1, 'Main', 'main'), (2, 'Other', 'other')");
  43. $db->execute("INSERT INTO board_columns (id, board_id, name, position) VALUES
  44. (1, 1, 'Queued', 0), (2, 1, 'Printing', 1), (3, 2, 'Other column', 0)");
  45. $db->execute("INSERT INTO swim_lanes (id, board_id, name, position) VALUES
  46. (1, 1, 'Standard', 0), (2, 1, 'Urgent', 1), (3, 2, 'Other row', 0)");
  47. $db->execute("INSERT INTO cards (id, board_id, column_id, swim_lane_id, position, cell_entered_at)
  48. VALUES (1, 1, 1, 1, 0, '2026-01-01 00:00:00')");
  49. putenv('APP_DEBUG=0');
  50. date_default_timezone_set('America/New_York');
  51. $_SESSION['auth_user'] = ['preferred_username' => 'test-user'];
  52. $controller = new CardsController();
  53. $move = static function (int $columnId, int $laneId, int $position = 0) use ($controller): Core\Response {
  54. return $controller->move(new Request([], [
  55. 'column_id' => (string) $columnId,
  56. 'swim_lane_id' => (string) $laneId,
  57. 'position' => (string) $position,
  58. ], ['REQUEST_METHOD' => 'POST']), 1);
  59. };
  60. check($move(2, 2)->status() === 200, 'Move should succeed.');
  61. $card = $db->first('SELECT column_id, swim_lane_id FROM cards WHERE id = 1');
  62. check((int) $card['column_id'] === 2 && (int) $card['swim_lane_id'] === 2, 'Card destination was not saved.');
  63. $history = $db->first('SELECT * FROM card_movements WHERE card_id = 1');
  64. check($history !== null, 'Movement was not recorded.');
  65. check($history['from_column_name'] === 'Queued' && $history['to_column_name'] === 'Printing', 'Column history is incorrect.');
  66. check($history['from_swim_lane_name'] === 'Standard' && $history['to_swim_lane_name'] === 'Urgent', 'Row history is incorrect.');
  67. check($history['moved_by'] === 'test-user', 'Move user was not recorded.');
  68. check(abs(strtotime($history['moved_at'] . ' UTC') - time()) < 10, 'Movement time was not saved in UTC.');
  69. $response = $controller->history(1);
  70. $recorded = json_decode($response->content(), true, flags: JSON_THROW_ON_ERROR)['movements'][0];
  71. check($recorded['to_column_name'] === 'Printing' && $recorded['to_swim_lane_name'] === 'Urgent', 'History API omitted the destination.');
  72. check($move(2, 2, 1)->status() === 200, 'Reordering should succeed.');
  73. check(count($db->query('SELECT id FROM card_movements WHERE card_id = 1')) === 1, 'Reordering added a movement.');
  74. check($move(2, 1)->status() === 200, 'Row-only move should succeed.');
  75. check(count($db->query('SELECT id FROM card_movements WHERE card_id = 1')) === 2, 'Row-only move was not recorded.');
  76. check($move(3, 1)->status() === 422, 'Cross-board column should be rejected.');
  77. check($move(2, 3)->status() === 422, 'Cross-board row should be rejected.');
  78. $card = $db->first('SELECT column_id, swim_lane_id FROM cards WHERE id = 1');
  79. check((int) $card['column_id'] === 2 && (int) $card['swim_lane_id'] === 1, 'Rejected move changed the card.');
  80. check(count($db->query('SELECT id FROM card_movements WHERE card_id = 1')) === 2, 'Rejected move changed history.');
  81. $db->execute("CREATE TRIGGER reject_movement BEFORE INSERT ON card_movements
  82. BEGIN SELECT RAISE(ABORT, 'forced history failure'); END");
  83. $logFile = tempnam(sys_get_temp_dir(), 'card_move_');
  84. $previousLog = ini_get('error_log');
  85. ini_set('error_log', $logFile);
  86. try {
  87. check($move(1, 1)->status() === 500, 'History failure should reject the move.');
  88. $card = $db->first('SELECT column_id, swim_lane_id FROM cards WHERE id = 1');
  89. check((int) $card['column_id'] === 2 && (int) $card['swim_lane_id'] === 1, 'Failed history write did not roll back the move.');
  90. check(count($db->query('SELECT id FROM card_movements WHERE card_id = 1')) === 2, 'Failed move changed history.');
  91. } finally {
  92. ini_set('error_log', $previousLog ?: '');
  93. unlink($logFile);
  94. $db->execute('DROP TRIGGER reject_movement');
  95. }
  96. echo "PASS: card moves record column and row history\n";

Powered by TurnKey Linux.