Skip to content
Open
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
166 changes: 166 additions & 0 deletions tests/functional/SSO_Functional_Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

use WP_Ultimo\SSO\SSO;
use WP_Ultimo\SSO\SSO_Broker;
use WP_Ultimo\SSO\Exception\SSO_Session_Exception;

/**
* Functional-style tests for SSO behaviors without full redirect flows.
*/
class SSO_Functional_Test extends \WP_UnitTestCase {

protected function setUp(): void {
parent::setUp();
// Ensure SSO is available.
SSO::get_instance();
// Default enable SSO during these tests.
add_filter('wu_sso_enabled', '__return_true');
// Stabilize salt across full suite.
add_filter(
'wu_sso_salt',
function () {
return 'testsalt';
}
);
}

protected function tearDown(): void {
remove_all_filters('wu_sso_enabled');
remove_all_filters('wu_sso_get_url_path');
remove_all_filters('wu_sso_salt');
parent::tearDown();
}

public function test_custom_url_path_filter_applies(): void {
add_filter(
'wu_sso_get_url_path',
function ($default, $action) {

Check warning on line 37 in tests/functional/SSO_Functional_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

It is recommended not to use reserved keyword "default" as function parameter name. Found: $default

Check warning on line 37 in tests/functional/SSO_Functional_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $action is never used

Check warning on line 37 in tests/functional/SSO_Functional_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $default is never used
// Always return a custom base; SSO appends the action suffix.
return 'custom';
},
10,
2
);

$sso = SSO::get_instance();

$this->assertSame('custom', $sso->get_url_path());
$this->assertSame('custom-sso', $sso->get_url_path('sso'));

$url = 'https://example.com/app';
$with = SSO::with_sso($url);
$this->assertStringContainsString('custom=login', $with);
}

public function test_calculate_secret_from_date_valid_and_invalid(): void {
$sso = SSO::get_instance();
$secret = $sso->calculate_secret_from_date('2024-01-01 00:00:00');
$this->assertIsString($secret);
$this->assertNotEmpty($secret);

$this->expectException(\WP_Ultimo\SSO\Exception\SSO_Exception::class);
$sso->calculate_secret_from_date('not-a-date');
}

public function test_get_broker_by_id_roundtrip_domains_and_secret(): void {
$blog_id = 1;
if (! get_site($blog_id)) {
$this->markTestSkipped('Main site not available in this environment.');
}
switch_to_blog($blog_id);
try {
$sso = SSO::get_instance();
$salt = $sso->salt();
$coded = $sso->encode($blog_id, $salt);

$info = $sso->get_broker_by_id($coded);
} finally {
restore_current_blog();
}
$this->assertIsArray($info);
$this->assertArrayHasKey('secret', $info);
$this->assertArrayHasKey('domains', $info);
$this->assertNotEmpty($info['domains']);

// Invalid should return null
$this->assertNull($sso->get_broker_by_id('invalid'));
}

public function test_get_final_return_url_builds_login_url_with_done_and_redirect(): void {
$sso = SSO::get_instance();
$base = network_home_url('/some/path');
$url = add_query_arg(
[
$sso->get_url_path() => 'login',
'redirect_to' => rawurlencode('https://example.com/after'),
],
$base
);

$final = $sso->get_final_return_url($url);
$this->assertStringContainsString($sso->get_url_path() . '=done', $final);
$this->assertStringContainsString('redirect_to=', $final);
$this->assertStringContainsString('wp-login.php', $final);
}

public function test_broker_attach_url_contains_token_broker_checksum_and_params(): void {
// Work on a dedicated site to avoid pollution from other tests.
$blog_id = 1;
switch_to_blog($blog_id);
try {
$sso = SSO::get_instance();
// Build a broker like SSO::get_broker but with absolute URL and in-memory state.
$blog = get_blog_details($blog_id);
$date = $blog ? $blog->registered : '2024-01-01 00:00:00';
$secret = $sso->calculate_secret_from_date($date);
$url = trailingslashit(network_home_url()) . $sso->get_url_path('grant');
$broker_id = $sso->encode($blog_id, $sso->salt());
$broker = new SSO_Broker($url, $broker_id, $secret);
} finally {
restore_current_blog();
}
// Avoid headers by storing token/verify in memory rather than cookies.
$broker = $broker->withTokenIn(new \ArrayObject());

$attach = $broker->getAttachUrl(['return_url' => 'https://example.com/here']);
$parts = wp_parse_url($attach);
parse_str($parts['query'] ?? '', $q);

$this->assertArrayHasKey('broker', $q);
$this->assertArrayHasKey('token', $q);
$this->assertArrayHasKey('checksum', $q);
$this->assertSame('https://example.com/here', $q['return_url']);

// JSONP variant
$attach2 = $broker->getAttachUrl(['_jsonp' => '1']);
$parts2 = wp_parse_url($attach2);
parse_str($parts2['query'] ?? '', $q2);
$this->assertSame('1', $q2['_jsonp']);
}

public function test_session_handler_start_throws_when_not_logged_in(): void {
$this->expectException(SSO_Session_Exception::class);
$this->expectExceptionCode(401);

$sso = SSO::get_instance();
unset($_REQUEST['broker']);
$handler = new \WP_Ultimo\SSO\SSO_Session_Handler($sso);
$handler->start();
}

public function test_get_return_type_defaults_and_values(): void {
$sso = SSO::get_instance();

unset($_REQUEST['return_type']);
$this->assertSame('redirect', $sso->get_return_type());

$_REQUEST['return_type'] = 'jsonp';
$this->assertSame('jsonp', $sso->get_return_type());

$_REQUEST['return_type'] = 'json';
$this->assertSame('json', $sso->get_return_type());

$_REQUEST['return_type'] = 'invalid';
$this->assertSame('redirect', $sso->get_return_type());
}
}
82 changes: 82 additions & 0 deletions tests/unit/SSO_Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

Check failure on line 1 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Missing file doc comment

use WP_Ultimo\SSO\SSO;
use WP_Ultimo\SSO\SSO_Session_Handler;

/**
* SSO unit tests covering helpers and session handler.
*/
class SSO_Test extends \WP_UnitTestCase {

public function setUp(): void {

Check failure on line 11 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Missing doc comment for function setUp()

Check warning on line 11 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Possible useless method overriding detected
parent::setUp();
// Ensure SSO singleton is initialized fresh per test when needed.
// SSO hooks only run if enabled; our tests use direct methods.
}

public function tearDown(): void {

Check failure on line 17 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Missing doc comment for function tearDown()
// Remove any filters set in tests.
remove_all_filters('wu_sso_enabled');
remove_all_filters('wu_sso_get_url_path');
remove_all_filters('determine_current_user');
parent::tearDown();
}

public function test_with_sso_appends_query_param_when_enabled(): void {

Check failure on line 25 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Missing doc comment for function test_with_sso_appends_query_param_when_enabled()
add_filter('wu_sso_enabled', '__return_true');
$url = 'https://example.com/path?foo=bar';
$withSso = SSO::with_sso($url);

Check failure on line 28 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable "$withSso" is not in valid snake_case format, try "$with_sso"

$this->assertStringContainsString('sso=login', $withSso, 'SSO query arg should be added');

Check failure on line 30 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable "$withSso" is not in valid snake_case format, try "$with_sso"
$this->assertStringContainsString('foo=bar', $withSso, 'Original query args should be preserved');

Check failure on line 31 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable "$withSso" is not in valid snake_case format, try "$with_sso"
}

public function test_with_sso_returns_same_url_when_disabled(): void {

Check failure on line 34 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Missing doc comment for function test_with_sso_returns_same_url_when_disabled()
add_filter('wu_sso_enabled', '__return_false');
$url = 'https://example.com/path?foo=bar';
$withSso = SSO::with_sso($url);

Check failure on line 37 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable "$withSso" is not in valid snake_case format, try "$with_sso"

$this->assertSame($url, $withSso, 'URL should be unchanged when SSO disabled');

Check failure on line 39 in tests/unit/SSO_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable "$withSso" is not in valid snake_case format, try "$with_sso"
}

public function test_encode_decode_roundtrip_uses_hashids(): void {
$sso = SSO::get_instance();
$salt = $sso->salt();
$value = 12345;

$encoded = $sso->encode($value, $salt);
$this->assertIsString($encoded);
$this->assertNotSame((string) $value, $encoded, 'Encoded value should be obfuscated');

$decoded = $sso->decode($encoded, $salt);
$this->assertSame($value, $decoded, 'Decoded value should match original');
}

public function test_session_handler_start_and_resume_sets_target_user_id(): void {
// Create a user and ensure WP recognizes it as current.
$user_id = self::factory()->user->create();
if (! $user_id) {
// Fallback to default admin user often present in WP tests.
$user_id = 1;
}

// Ensure we have a site id to encode as broker id.
$site_id = get_current_blog_id();
$sso = SSO::get_instance();
$salt = $sso->salt();
$broker = $sso->encode($site_id, $salt);

// Simulate request param that SSO_Session_Handler reads.
$_REQUEST['broker'] = $broker;

$handler = new SSO_Session_Handler($sso);

// Simulate the broker session storage that start() would create.
set_site_transient("sso-{$broker}-{$site_id}", $user_id, 180);

// resume() should read the transient and set target user id inside SSO.
$handler->resume($broker);

$this->assertSame($user_id, $sso->get_target_user_id(), 'Target user id should be set from session');
}
}
Loading