aboutsummaryrefslogblamecommitdiffstats
path: root/tests/unit/Module/AdminAccountEditTest.php
blob: 818f30f269fd90f1d2033fcd444dc526c3a38048 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                  


                                             








                                              




































































































































































































                                                                                                                          
<?php
/* Tests for Account_edit module
 *
 * SPDX-FileCopyrightText: 2024 Hubzilla Community
 * SPDX-FileContributor: Harald Eilertsen
 *
 * SPDX-License-Identifier: MIT
 */

namespace Zotlabs\Tests\Unit\Module;

use DateTimeImmutable;
use PHPUnit\Framework\Attributes\{Before, After};

class AdminAccountEditTest extends TestCase {

	private $stub_is_site_admin;
	private $stub_info;
	private $stub_notice;
	private $stub_check_security;
	private $stub_get_form_security_token;

	private array $info;
	private array $notice;

	#[Before]
	public function setup_mocks(): void {
		/*
		 * As we're testing pages that should only be reachable by the
		 * site admin, it makes no sense to have it return anything else
		 * than true.
		 */
		$this->stub_is_site_admin =
			$this->getFunctionMock('Zotlabs\Module', 'is_site_admin')
				->expects($this->once())
				->willReturn(true);

		$this->info = [];
		$this->stub_info =
			$this->getFunctionMock('Zotlabs\Module\Admin', 'info')
				->expects($this->any())
				->willReturnCallback(function (string $arg) {
					$this->info[] = $arg;
				});

		$this->notice = [];
		$this->stub_notice =
			$this->getFunctionMock('Zotlabs\Module\Admin', 'notice')
				->expects($this->any())
				->willReturnCallback(function (string $arg) {
					$this->notice[] = $arg;
				});

	}

	#[After]
	public function tear_down_mocks(): void {
		$this->stub_is_site_admin = null;
		$this->stub_info = null;
		$this->stub_notice = null;
		$this->stub_check_security = null;
		$this->stub_get_form_security_token = null;
	}

	public function test_rendering_admin_account_edit_page(): void {
		$this->stub_get_form_security_token =
			$this->getFunctionMock('Zotlabs\Module\Admin', 'get_form_security_token')
				->expects($this->once())
				->willReturn('the-csrf-token');

		$account = $this->fixtures['account'][0];

		$this->get("admin/account_edit/{$account['account_id']}");

		$this->assertPageContains("<form action=\"admin/account_edit/{$account['account_id']}\" method=\"post\"");
		$this->assertPageContains($account['account_email']);

		// Check that we generate a CSRF token for the form
		$this->assertPageContains("<input type=\"hidden\" name=\"security\" value=\"the-csrf-token\"");
	}

	public function test_rendering_admin_account_edit_page_fails_if_id_is_not_found(): void {
		$this->get("admin/account_edit/666");

		$this->assertEquals('', \App::$page['content']);
	}

	public function test_rendering_admin_account_edit_page_fails_if_id_is_not_numeric(): void {
		$this->get("admin/account_edit/66invalid");

		$this->assertEquals('', \App::$page['content']);
	}

	public function test_post_empty_form_does_not_modify_account(): void {
		$this->stub_goaway();
		$this->stub_check_form_security(true);

		$account = get_account_by_id($this->fixtures['account'][0]['account_id']);

		try {
			$this->post(
				"admin/account_edit/{$account['account_id']}",
				[],
				[
					'aid' => $account['account_id'],
					'pass1' => '',
					'pass2' => '',
					'service_class' => $account['account_service_class'],
					'account_language' => $account['account_language'],
					'security' => 'The security token',
				]
			);
		} catch (RedirectException $ex) {
			$this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage());
		}

		$reloaded = get_account_by_id($account['account_id']);

		$this->assertEquals($account, $reloaded);

		// Not sure if this is expected behaviour, but this is how it is today.
		$this->assertContains('Account settings updated.' . EOL, $this->info);
	}

	public function test_post_form_changes_account(): void {
		$this->stub_goaway();
		$this->stub_check_form_security(true);

		// clone account from fixture, to ensure it's not replaced with
		// the reloaded one below.
		$account = get_account_by_id($this->fixtures['account'][0]['account_id']);

		try {
			$this->post(
				"admin/account_edit/{$account['account_id']}",
				[],
				[
					'aid' => $account['account_id'],
					'pass1' => 'hunter2',
					'pass2' => 'hunter2',
					'service_class' => 'Some other class',
					'account_language' => 'nn',
					'security' => 'The security token',
				]
			);
		} catch (RedirectException $ex) {
			$this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage());
		}

		$reloaded = get_account_by_id($account['account_id']);

		$this->assertNotEquals($account, $reloaded);
		$this->assertEquals('Some other class', $reloaded['account_service_class']);
		$this->assertEquals('nn', $reloaded['account_language']);

		$now = new DateTimeImmutable('now');
		$this->assertEquals($now->format('Y-m-d H:i:s'), $reloaded['account_password_changed']);

		$this->assertContains('Account settings updated.' . EOL, $this->info);
		$this->assertContains("Password changed for account {$account['account_id']}." . EOL, $this->info);
	}

	public function test_form_with_missing_or_incalid_csrf_token_is_rejected(): void {
		$this->expectException(KillmeException::class);

		// Emulate a failed CSRF check
		$this->stub_check_form_security(false);

		$account_id = $this->fixtures['account'][0]['account_id'];

		$this->post(
			"admin/account_edit/{$account_id}",
			[],
			[
				'aid' => $account_id,
				'pass1' => 'hunter2',
				'pass2' => 'hunter2',
				'service_class' => 'Some other class',
				'account_language' => 'nn',
				'security' => 'Invalid security token',
			]
		);
	}

	/*
	 * Override the stub_goaway method because we need the stub to live in the
	 * Admin namespace.
	 */
	protected function stub_goaway(): void {
		$this->goaway_stub = $this->getFunctionMock('Zotlabs\Module\Admin', 'goaway')
			->expects($this->once())
			->willReturnCallback(
				function (string $uri) {
					throw new RedirectException($uri);
				}
			);
	}

	/**
	 * Stub the check_form_security_token_ForbiddenOnErr.
	 *
	 * In these tests we're not really interested in _how_ the form security
	 * tokens work, but that the code under test perform the checks. This stub
	 * allows us to do that without having to worry if everything is set up so
	 * that the real function would work or not.
	 *
	 * @param bool $valid		true if emulating a valid token, false otherwise.
	 */
	protected function stub_check_form_security(bool $valid): void {
		$this->stub_check_security =
			$this->getFunctionMock('Zotlabs\Module\Admin', 'check_form_security_token_ForbiddenOnErr')
				->expects($this->once())
				->with(
					$this->identicalTo('admin_account_edit'),
					$this->identicalTo('security'))
				->willReturnCallback(function () use ($valid) {
					if (! $valid) {
						throw new KillmeException();
					}
				});
	}
}