Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION';
SET FOREIGN_KEY_CHECKS = 1;


-- ---------------------------------------------------------------------
-- 0. users (kimlik dogrulama)
--
-- BOS veritabaninda calisan kurulum icin BURADA olusturulur:
-- asagidaki ilk tablo (departments) manager_id uzerinden users(id)'ye
-- FK icerir; tablo yoksa kurulum ERROR 1005 (errno 150) ile kirilir.
-- Bolum 10'daki ALTER'lar yukseltmeler icin aynen kalir (IF NOT EXISTS
-- ile idempotent) ve bu tabloyu departman/title/phone/
-- must_change_password/password_changed_at/created_by kolonlariyle
-- zenginlestirir.
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL,
password VARCHAR(255) NOT NULL,
role ENUM('admin','manager','analyst','viewer') NOT NULL DEFAULT 'viewer',
status TINYINT(1) NOT NULL DEFAULT 1,
last_login_at DATETIME NULL DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


-- ---------------------------------------------------------------------
-- 1. departments
-- ---------------------------------------------------------------------
Expand Down Expand Up @@ -311,8 +337,9 @@ CREATE TABLE IF NOT EXISTS login_attempts (


-- ---------------------------------------------------------------------
-- 10. users -> MEVCUT TABLO ALTER EDILIYOR (SILINMIYOR)
-- departments tablosu olustuktan SONRA calismali.
-- 10. users -> bolum 0'da olusturulan tablo burada ALTER edilir
-- (mevcut kurulumlar korunur; departments tablosu olustuktan
-- SONRA calismali - fk_users_department ona baglidir).
-- ---------------------------------------------------------------------
ALTER TABLE users
ADD COLUMN IF NOT EXISTS department_id INT UNSIGNED NULL DEFAULT NULL AFTER role,
Expand Down
14 changes: 14 additions & 0 deletions database/seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ VALUES
'Yazdirilan raporlarin altinda gorunen aciklama satiri.', 1, 80);


-- ---------------------------------------------------------------------
-- ILK ADMIN KULLANICISI
-- README: "Ilk giris: admin@riskops.local / Admin123456 - uygulama
-- ilk giriste parola degistirmeye zorlar." Bu satir olmazsa belgelenmis
-- kurulum sonunda sisteme giris yapilabilecek hicbir hesap olusmaz
-- (asagidaki UPDATE yalnizca mevcut admin kaydini departmana baglar).
-- Parola bcrypt ile hashlenmistir; ilk giriste degisim zorunludur.
-- ---------------------------------------------------------------------
INSERT IGNORE INTO users (name, email, password, role, status, must_change_password)
VALUES ('System Administrator', 'admin@riskops.local',
'$2y$12$YOWfKRyFQrinKDSIU50zHOQww8udcyN6zQm7BmKuWcp41NkLv8SBS',
'admin', 1, 1);


-- ---------------------------------------------------------------------
-- MEVCUT ADMIN KULLANICISINI IT departmanina bagla
-- (yalnizca departmani bos ise - kullanici secimini ezmez)
Expand Down
186 changes: 186 additions & 0 deletions tools/fresh_bootstrap_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);

/**
* RiskOps - Bos veritabani kurulum (fresh bootstrap) regresyon testi
* Calistirma: php tools/fresh_bootstrap_test.php
*
* BAGIMLILIK: yerel MariaDB (Docker konteyneri "riskops-mariadb",
* 127.0.0.1:3306, root erisimi). Sunucu yoksa test ACIKCA basarisiz olur
* (tools/smoke_test.php ile ayni davranis).
*
* REGRESYON ARKAPLANI: repoda bir zamanlar CREATE TABLE users YOKTU;
* schema.sql'in ilk tablosu (departments) users(id)'ye FK icerdigi icin
* bos veritabaninda belgelenmis kurulum ERROR 1005 (errno 150) ile
* kiriliyordu ve seed.sql ilk admini INSERT etmedigi icin kurulum sonunda
* giris yapilacak hesap olmuyordu. schema.sql bolum 0'da artik users
* tablosunu olusturuyor; seed.sql belgelenmis ilk admini ekliyor.
*
* Bu test GERCEK database/schema.sql ve database/seed.sql dosyalarini
* bos bir karalama veritabaninda calistirir (mock yok) ve ayrica dosyanin
* kendi sozu olan idempotentligi (ust uste tekrar calistirilabilirlik)
* dogrular.
*/

if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit('CLI only.');
}

const BOOTSTRAP_TEST_DB = 'riskops_bootstrap_test';
const BOOTSTRAP_TEST_ROOT_PASS = 'riskops_local_root';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The documented Ubuntu setup authenticates MariaDB root through sudo mysql, but this test forces TCP root login with a committed password that setup never creates. Read the connection credentials and socket from the environment, or use the configured test database account.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/fresh_bootstrap_test.php, line 31:

<comment>The documented Ubuntu setup authenticates MariaDB root through `sudo mysql`, but this test forces TCP root login with a committed password that setup never creates. Read the connection credentials and socket from the environment, or use the configured test database account.</comment>

<file context>
@@ -0,0 +1,186 @@
+}
+
+const BOOTSTRAP_TEST_DB = 'riskops_bootstrap_test';
+const BOOTSTRAP_TEST_ROOT_PASS = 'riskops_local_root';
+
+$expectedTables = [
</file context>


$expectedTables = [
'audit_logs', 'departments', 'login_attempts', 'risk_actions',
'risk_assessments', 'risk_categories', 'risk_sequences', 'risks',
'settings', 'users',
];

/** Gercek SQL dosyasini ifade ifade uygular; ilk hatayi dondurur. */
function bootstrap_run_sql_file(PDO $pdo, string $path): ?array
{
$raw = (string)file_get_contents($path);
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];

$stmt = '';
$count = 0;
foreach ($lines as $line) {
if (preg_match('/^\s*--/', $line) || trim($line) === '') {
continue;
}
$stmt .= $line . "\n";
if (preg_match('/;\s*$/', $line)) {
$count++;
try {
$pdo->exec(trim($stmt));
} catch (PDOException $e) {
return ['error' => $e->getMessage(), 'sql' => trim($stmt), 'stmtno' => $count];
}
$stmt = '';
}
}
return null;
}

$PASS = 0;
$FAIL = 0;

function check(string $label, bool $ok, string $detail = ''): void
{
global $PASS, $FAIL;
if ($ok) {
$PASS++;
printf(" [ OK ] %-56s %s\n", $label, $detail);
} else {
$FAIL++;
printf(" [FAIL] %-56s %s\n", $label, $detail);
}
}

function section(string $title): void
{
echo "\n" . str_repeat('-', 72) . "\n " . $title . "\n" . str_repeat('-', 72) . "\n";
}

echo "\n=============== Fresh Bootstrap Regression Test ===============";

/* --- Ortam ----------------------------------------------------------- */

try {
$root = new PDO(
'mysql:host=127.0.0.1;port=3306;charset=utf8mb4',
'root',
BOOTSTRAP_TEST_ROOT_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5]
);
} catch (Throwable $e) {
echo "\nYerel MariaDB'ye baglanilamadi (konteyner: docker start riskops-mariadb).\n";
echo 'Hata: ' . $e->getMessage() . "\n";
echo "FRESH BOOTSTRAP TEST: FAIL\n";
exit(1);
}

$root->exec('DROP DATABASE IF EXISTS ' . BOOTSTRAP_TEST_DB);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a developer already has a database named riskops_bootstrap_test, this test irreversibly deletes it before running. Generate a unique scratch database per run or refuse to proceed when the fixed name exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/fresh_bootstrap_test.php, line 104:

<comment>When a developer already has a database named `riskops_bootstrap_test`, this test irreversibly deletes it before running. Generate a unique scratch database per run or refuse to proceed when the fixed name exists.</comment>

<file context>
@@ -0,0 +1,186 @@
+    exit(1);
+}
+
+$root->exec('DROP DATABASE IF EXISTS ' . BOOTSTRAP_TEST_DB);
+$root->exec('CREATE DATABASE ' . BOOTSTRAP_TEST_DB
+    . ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
</file context>

$root->exec('CREATE DATABASE ' . BOOTSTRAP_TEST_DB
. ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
$pdo = new PDO(
'mysql:host=127.0.0.1;port=3306;dbname=' . BOOTSTRAP_TEST_DB . ';charset=utf8mb4',
'root',
BOOTSTRAP_TEST_ROOT_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);

/* ------------------------------------------------------------------ */
section('1) Bos veritabaninda belgelenmis kurulum');
/* ------------------------------------------------------------------ */

$err = bootstrap_run_sql_file($pdo, __DIR__ . '/../database/schema.sql');
check('schema.sql bos veritabaninda hatasiz uygulanir', $err === null,
$err !== null ? sprintf('#%d: %s', $err['stmtno'], $err['error']) : '');

if ($err === null) {
$tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
sort($tables);
check('10 tablo olusur', $tables === $expectedTables, count($tables) . ' tablo');

$err = bootstrap_run_sql_file($pdo, __DIR__ . '/../database/seed.sql');
check('seed.sql hatasiz uygulanir', $err === null,
$err !== null ? sprintf('#%d: %s', $err['stmtno'], $err['error']) : '');

$u = $pdo->query(
"SELECT password, role, status, must_change_password, department_id
FROM users WHERE email = 'admin@riskops.local' LIMIT 1"
)->fetch();

check('belgelenmis ilk admin olusur', is_array($u));
if (is_array($u)) {
check('admin rol/durum/parola-degisimi zorunlu',
$u['role'] === 'admin' && (int)$u['status'] === 1 && (int)$u['must_change_password'] === 1);
check('admin departmana bagli (IT)', $u['department_id'] !== null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This check labeled IT only verifies that department_id is non-null, so a seed regression linking the admin to Finance or another department still passes. Assert the joined department code is IT.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/fresh_bootstrap_test.php, line 141:

<comment>This check labeled IT only verifies that `department_id` is non-null, so a seed regression linking the admin to Finance or another department still passes. Assert the joined department code is `IT`.</comment>

<file context>
@@ -0,0 +1,186 @@
+    if (is_array($u)) {
+        check('admin rol/durum/parola-degisimi zorunlu',
+            $u['role'] === 'admin' && (int)$u['status'] === 1 && (int)$u['must_change_password'] === 1);
+        check('admin departmana bagli (IT)', $u['department_id'] !== null);
+        check("password_verify('Admin123456')",
+            password_verify('Admin123456', (string)$u['password']));
</file context>
Suggested change
check('admin departmana bagli (IT)', $u['department_id'] !== null);
check('admin departmana bagli (IT)',
(string)$pdo->query('SELECT code FROM departments WHERE id = ' . (int)$u['department_id'])->fetchColumn() === 'IT');

check("password_verify('Admin123456')",
password_verify('Admin123456', (string)$u['password']));
}
$settingsRows = (int)$pdo->query('SELECT COUNT(*) FROM settings')->fetchColumn();
check('ayarlar yuklendi', $settingsRows === 17, $settingsRows . ' satir');
}

/* ------------------------------------------------------------------ */
section('2) Idempotenti: ayni dosyalar ust uste tekrar calisir');
/* ------------------------------------------------------------------ */

$err = bootstrap_run_sql_file($pdo, __DIR__ . '/../database/schema.sql');
check('schema.sql 2. kez hatasiz uygulanir', $err === null,
$err !== null ? sprintf('#%d: %s', $err['stmtno'], $err['error']) : '');

$err = bootstrap_run_sql_file($pdo, __DIR__ . '/../database/seed.sql');
check('seed.sql 2. kez hatasiz uygulanir', $err === null,
$err !== null ? sprintf('#%d: %s', $err['stmtno'], $err['error']) : '');

$tables2 = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
sort($tables2);
check('tablo sayisi degismedi (10)', $tables2 === $expectedTables, count($tables2) . ' tablo');

$adminCount = (int)$pdo->query(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When schema.sql fails (the exact regression this test guards against), later $pdo->query(...)->fetch() calls on users throw an uncaught PDOException and the script crashes before the final OK/FAIL summary and exit code are printed. Guard the admin/settings and idempotency checks behind a schema-ok condition (or catch PDOException) so a regression yields a clean FAIL result instead of a fatal.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/fresh_bootstrap_test.php, line 165:

<comment>When schema.sql fails (the exact regression this test guards against), later `$pdo->query(...)->fetch()` calls on `users` throw an uncaught PDOException and the script crashes before the final OK/FAIL summary and exit code are printed. Guard the admin/settings and idempotency checks behind a schema-ok condition (or catch PDOException) so a regression yields a clean FAIL result instead of a fatal.</comment>

<file context>
@@ -0,0 +1,186 @@
+sort($tables2);
+check('tablo sayisi degismedi (10)', $tables2 === $expectedTables, count($tables2) . ' tablo');
+
+$adminCount = (int)$pdo->query(
+    "SELECT COUNT(*) FROM users WHERE email = 'admin@riskops.local'"
+)->fetchColumn();
</file context>

"SELECT COUNT(*) FROM users WHERE email = 'admin@riskops.local'"
)->fetchColumn();
check('admin kopyalanmadi (INSERT IGNORE)', $adminCount === 1, $adminCount . ' satir');

$settings2 = (int)$pdo->query('SELECT COUNT(*) FROM settings')->fetchColumn();
check('ayarlar kopyalanmadi', $settings2 === $settingsRows, $settings2 . ' satir');

/* --- Karalama veritabanini birak ------------------------------------ */

$root->exec('DROP DATABASE IF EXISTS ' . BOOTSTRAP_TEST_DB);

/* ------------------------------------------------------------------ */

echo "\n" . str_repeat('-', 72) . "\n";
printf("Sonuc: %d OK, %d FAIL\n", $PASS, $FAIL);
if ($FAIL > 0) {
echo "FRESH BOOTSTRAP TEST: FAIL\n";
exit(1);
}
echo "FRESH BOOTSTRAP TEST: PASS\n";
exit(0);