Initial import of space_profiles module

This commit is contained in:
Kelin Rescue Hub
2026-04-04 13:11:50 -04:00
commit 87a59e5a0a
35 changed files with 1627 additions and 0 deletions

19
tests/README.md Normal file
View File

@@ -0,0 +1,19 @@
# Space Profiles Test Notes
This module includes a unit suite scaffold under `tests/codeception/unit`.
Current unit coverage targets:
- HTML sanitization hardening regressions
- Public/profile access helper behavior
- Form-level validation behavior for core profile fields
## Run
In a HumHub development environment with Codeception installed:
```bash
cd protected
php vendor/bin/codecept run -c modules/space_profiles/tests/codeception.yml unit
```
If `vendor/bin/codecept` is not present, install dev dependencies for the HumHub environment first.

18
tests/codeception.yml Normal file
View File

@@ -0,0 +1,18 @@
actor: Tester
namespace: space_profiles
bootstrap: _bootstrap.php
settings:
suite_class: \PHPUnit_Framework_TestSuite
colors: true
shuffle: false
memory_limit: 1024M
log: true
backup_globals: true
paths:
tests: codeception
log: codeception/_output
data: codeception/_data
helpers: codeception/_support
envs: ../../../humhub/tests/config/env
config:
test_entry_url: http://localhost:8080/index-test.php

View File

@@ -0,0 +1,26 @@
<?php
/*
* Initial module test bootstrap. Loads default HumHub test bootstrap.
*/
$testRoot = dirname(__DIR__);
\Codeception\Configuration::append(['test_root' => $testRoot]);
codecept_debug('Module root: ' . $testRoot);
$humhubPath = getenv('HUMHUB_PATH');
if ($humhubPath === false) {
$moduleConfig = require $testRoot . '/config/test.php';
if (isset($moduleConfig['humhub_root'])) {
$humhubPath = $moduleConfig['humhub_root'];
} else {
$humhubPath = dirname(__DIR__, 5);
}
}
\Codeception\Configuration::append(['humhub_root' => $humhubPath]);
codecept_debug('HumHub Root: ' . $humhubPath);
$globalConfig = require $humhubPath . '/protected/humhub/tests/codeception/_loadConfig.php';
require $globalConfig['humhub_root'] . '/protected/humhub/tests/codeception/_bootstrap.php';

View File

@@ -0,0 +1,7 @@
<?php
namespace space_profiles;
class UnitTester extends \humhub\tests\codeception\_support\HumHubDbTestCase
{
}

View File

@@ -0,0 +1,3 @@
<?php
return \tests\codeception\_support\HumHubTestConfiguration::getSuiteConfig('unit');

View File

@@ -0,0 +1,9 @@
class_name: UnitTester
modules:
enabled:
- tests\codeception\_support\CodeHelper
- Yii2
config:
Yii2:
configFile: 'codeception/config/unit.php'
transaction: false

View File

@@ -0,0 +1,69 @@
<?php
namespace space_profiles;
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\helpers\ProfileAccess;
use humhub\modules\user\models\User;
class ProfileAccessTest extends UnitTester
{
public function testPublicSpaceAllowsGuest(): void
{
$space = $this->createSpaceMock(Space::VISIBILITY_ALL, true, false);
$this->assertTrue(ProfileAccess::canView($space, null));
}
public function testMembersOnlyBlocksGuestAndAllowsUser(): void
{
$space = $this->createSpaceMock(Space::VISIBILITY_REGISTERED_ONLY, true, false);
$user = $this->createUserMock(false);
$this->assertFalse(ProfileAccess::canView($space, null));
$this->assertTrue(ProfileAccess::canView($space, $user));
}
public function testPrivateSpaceRequiresMembership(): void
{
$user = $this->createUserMock(false);
$nonMemberSpace = $this->createSpaceMock(Space::VISIBILITY_NONE, true, false);
$memberSpace = $this->createSpaceMock(Space::VISIBILITY_NONE, true, true);
$this->assertFalse(ProfileAccess::canView($nonMemberSpace, $user));
$this->assertTrue(ProfileAccess::canView($memberSpace, $user));
}
public function testDisabledSpaceDenied(): void
{
$space = $this->createSpaceMock(Space::VISIBILITY_ALL, false, true);
$this->assertFalse(ProfileAccess::canView($space, $this->createUserMock(true)));
}
private function createSpaceMock(int $visibility, bool $enabled, bool $isMember): Space
{
$space = $this->getMockBuilder(Space::class)
->disableOriginalConstructor()
->onlyMethods(['isMember'])
->getMock();
$space->visibility = $visibility;
$space->status = $enabled ? Space::STATUS_ENABLED : Space::STATUS_ARCHIVED;
$space->method('isMember')->willReturn($isMember);
return $space;
}
private function createUserMock(bool $isAdmin): User
{
$user = $this->getMockBuilder(User::class)
->disableOriginalConstructor()
->onlyMethods(['isSystemAdmin'])
->getMock();
$user->method('isSystemAdmin')->willReturn($isAdmin);
return $user;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace space_profiles;
use humhub\modules\space_profiles\helpers\ProfileHtmlSanitizer;
class ProfileHtmlSanitizerTest extends UnitTester
{
public function testSanitizeRemovesScriptsAndHandlers(): void
{
$input = '<script>alert(1)</script><div class="hdr" onclick="alert(2)">OK</div>';
$output = ProfileHtmlSanitizer::sanitize($input);
$this->assertStringNotContainsString('<script', $output);
$this->assertStringNotContainsString('onclick=', $output);
$this->assertStringContainsString('<div class="hdr">OK</div>', $output);
}
public function testSanitizeStripsJavascriptUris(): void
{
$input = '<a href="javascript:alert(1)">bad</a><a href="https://example.org">good</a>';
$output = ProfileHtmlSanitizer::sanitize($input);
$this->assertStringNotContainsString('javascript:', $output);
$this->assertStringContainsString('https://example.org', $output);
}
public function testSanitizeScopesEmbeddedCss(): void
{
$input = '<style>body{color:red}.hdr{font-weight:bold}</style><div class="hdr">Title</div>';
$output = ProfileHtmlSanitizer::sanitize($input);
$this->assertStringContainsString('.rescue-profile-scope', $output);
$this->assertStringContainsString('.rescue-profile-scope .hdr', $output);
$this->assertStringNotContainsString('<style>body{color:red}', $output);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace space_profiles;
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\models\forms\SpaceProfileForm;
class SpaceProfileFormTest extends UnitTester
{
public function testValidMinimalDataPassesValidation(): void
{
$space = Space::findOne(['id' => 2]);
$this->assertNotNull($space, 'Expected fixture space with id=2 to exist.');
$form = new SpaceProfileForm(['contentContainer' => $space]);
$form->profile_name = 'Test Rescue Space';
$form->city = 'Boston';
$form->state = 'MA';
$form->zip_code = '02110';
$form->contact_email = 'team@example.org';
$form->contact_phone = '(617) 555-1212';
$form->website_url = 'https://example.org';
$this->assertTrue($form->validate());
}
public function testStateMustBeTwoCharacters(): void
{
$space = Space::findOne(['id' => 2]);
$this->assertNotNull($space, 'Expected fixture space with id=2 to exist.');
$form = new SpaceProfileForm(['contentContainer' => $space]);
$form->profile_name = 'Invalid State Case';
$form->state = 'Massachusetts';
$this->assertFalse($form->validate(['state']));
$this->assertArrayHasKey('state', $form->getErrors());
}
public function testContactEmailMustBeValid(): void
{
$space = Space::findOne(['id' => 2]);
$this->assertNotNull($space, 'Expected fixture space with id=2 to exist.');
$form = new SpaceProfileForm(['contentContainer' => $space]);
$form->profile_name = 'Invalid Email Case';
$form->contact_email = 'not-an-email';
$this->assertFalse($form->validate(['contact_email']));
$this->assertArrayHasKey('contact_email', $form->getErrors());
}
}

View File

@@ -0,0 +1 @@
<?php

6
tests/config/common.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
/*
* Shared config for all suites.
*/
return [];

8
tests/config/test.php Normal file
View File

@@ -0,0 +1,8 @@
<?php
return [
'modules' => ['space_profiles'],
'fixtures' => [
'default',
],
];

3
tests/config/unit.php Normal file
View File

@@ -0,0 +1,3 @@
<?php
return [];