firstResult; } public function setFirstResult(?array $result): void { $this->firstResult = $result; } public function update(int $id, array $data): bool { $this->lastUpdate = ['id' => $id, 'data' => $data]; $this->findResults[$id] = ['id' => $id] + $data; return true; } public function insert(array $data): int { $id = $this->nextInsertId++; $this->lastInsertData = $data; $this->findResults[$id] = ['id' => $id] + $data; return $id; } public function find(int $id): ?array { return $this->findResults[$id] ?? null; } public function setFindResult(int $id, array $result): void { $this->findResults[$id] = $result; } } class FakeJsonRequest extends MockIncomingRequest { public function __construct(private readonly ?array $payload) { parent::__construct(config(App::class), new URI('https://test.alrahmaisgl.org'), null, new UserAgent()); } public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0): ?array { return $this->payload; } } class TestablePreferencesController extends PreferencesController { private ?object $currentUser = null; public function __construct() { // Skip the real constructor to avoid bootstrapping models. } public function setPreferencesModel(StubPreferencesModel $model): void { $this->preferencesModel = $model; } public function setCurrentUser(?object $user): void { $this->currentUser = $user; } protected function getCurrentUser(): ?object { return $this->currentUser; } } class PreferencesControllerTest extends CIUnitTestCase { private TestablePreferencesController $controller; private StubPreferencesModel $preferencesModel; protected function setUp(): void { parent::setUp(); Services::resetSingle('validation'); Services::resetSingle('session'); $this->preferencesModel = new StubPreferencesModel(); $this->controller = new TestablePreferencesController(); $this->controller->setPreferencesModel($this->preferencesModel); } public function testIndexRequiresAuthentication() { $this->controller->setCurrentUser(null); $response = $this->controller->index(); $this->assertFalse($response['status']); $this->assertSame(401, $response['code']); $this->assertSame('Authentication required', $response['message']); } public function testIndexReturnsDefaultsIfNoPreferences() { $this->controller->setCurrentUser((object) ['id' => 7]); $this->preferencesModel->setFirstResult(null); $response = $this->controller->index(); $this->assertTrue($response['status']); $this->assertSame('Preferences retrieved successfully', $response['message']); $this->assertSame(1, $response['data']['notification_email']); $this->assertSame('light', $response['data']['theme']); $this->assertSame('blue', $response['data']['style_color']); $this->assertSame('white', $response['data']['menu_color']); } public function testIndexPropagatesStoredPreferencesAndSessionStyles() { $this->controller->setCurrentUser((object) ['id' => 9]); $this->preferencesModel->setFirstResult([ 'id' => 11, 'user_id' => 9, 'notification_email' => 0, 'notification_sms' => 0, 'theme' => 'dark', 'language' => 'fr', ]); session()->set('style_color', 'rose'); session()->set('menu_color', 'navy'); $response = $this->controller->index(); $this->assertTrue($response['status']); $this->assertSame(0, $response['data']['notification_email']); $this->assertSame('rose', $response['data']['style_color']); $this->assertSame('navy', $response['data']['menu_color']); } public function testUpdateReturnsValidationErrors() { $this->controller->setCurrentUser((object) ['id' => 3]); $this->setRequestPayload(['notification_email' => '2']); $response = $this->controller->update(); $this->assertFalse($response['status']); $this->assertSame(422, $response['code']); $this->assertSame('Validation failed', $response['message']); $this->assertArrayHasKey('errors', $response); } public function testUpdateInsertsWhenPreferencesAbsent() { $this->controller->setCurrentUser((object) ['id' => 12]); $this->preferencesModel->setFirstResult(null); $payload = [ 'notification_email' => 0, 'notification_sms' => 1, 'theme' => 'dark', 'language' => 'es', 'style_color' => 'green', 'menu_color' => 'brand', ]; $this->setRequestPayload($payload); $response = $this->controller->update(); $this->assertTrue($response['status']); $this->assertSame('Preferences updated successfully', $response['message']); $this->assertSame(0, $response['data']['notification_email']); $this->assertSame('green', session()->get('style_color')); $this->assertSame('brand', session()->get('menu_color')); $this->assertSame($this->preferencesModel->lastInsertData, [ 'user_id' => 12, 'notification_email' => 0, 'notification_sms' => 1, 'theme' => 'dark', 'language' => 'es', ]); } public function testUpdateModifiesExistingPreferences() { $this->controller->setCurrentUser((object) ['id' => 21]); $this->preferencesModel->setFirstResult([ 'id' => 18, 'user_id' => 21, 'notification_email' => 1, 'notification_sms' => 1, 'theme' => 'light', 'language' => 'en', ]); $this->preferencesModel->setFindResult(18, [ 'id' => 18, 'user_id' => 21, 'notification_email' => 1, 'notification_sms' => 1, 'theme' => 'light', 'language' => 'en', ]); $payload = ['notification_email' => 1, 'notification_sms' => 0, 'language' => 'fr']; $this->setRequestPayload($payload); $response = $this->controller->update(); $this->assertTrue($response['status']); $this->assertSame('Preferences updated successfully', $response['message']); $this->assertSame(1, $this->preferencesModel->lastUpdate['data']['notification_email']); $this->assertSame(0, $this->preferencesModel->lastUpdate['data']['notification_sms']); $this->assertSame(18, $this->preferencesModel->lastUpdate['id']); $this->assertSame('fr', $response['data']['language']); } private function setRequestPayload(?array $payload): void { $request = new FakeJsonRequest($payload); $this->controller->setRequest($request); } }