Skip to content

Commit a4d6ab1

Browse files
authored
feat: Add more tests for SSO (#211)
* feat: Add more tests for SSO * style: run phpcbf
1 parent f03d221 commit a4d6ab1

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
use WP_Ultimo\SSO\SSO;
4+
use WP_Ultimo\SSO\SSO_Broker;
5+
use WP_Ultimo\SSO\Exception\SSO_Session_Exception;
6+
7+
/**
8+
* Functional-style tests for SSO behaviors without full redirect flows.
9+
*/
10+
class SSO_Functional_Test extends \WP_UnitTestCase {
11+
12+
protected function setUp(): void {
13+
parent::setUp();
14+
// Ensure SSO is available.
15+
SSO::get_instance();
16+
// Default enable SSO during these tests.
17+
add_filter('wu_sso_enabled', '__return_true');
18+
// Stabilize salt across full suite.
19+
add_filter(
20+
'wu_sso_salt',
21+
function () {
22+
return 'testsalt';
23+
}
24+
);
25+
}
26+
27+
protected function tearDown(): void {
28+
remove_all_filters('wu_sso_enabled');
29+
remove_all_filters('wu_sso_get_url_path');
30+
remove_all_filters('wu_sso_salt');
31+
parent::tearDown();
32+
}
33+
34+
public function test_custom_url_path_filter_applies(): void {
35+
add_filter(
36+
'wu_sso_get_url_path',
37+
function ($default, $action) {
38+
// Always return a custom base; SSO appends the action suffix.
39+
return 'custom';
40+
},
41+
10,
42+
2
43+
);
44+
45+
$sso = SSO::get_instance();
46+
47+
$this->assertSame('custom', $sso->get_url_path());
48+
$this->assertSame('custom-sso', $sso->get_url_path('sso'));
49+
50+
$url = 'https://example.com/app';
51+
$with = SSO::with_sso($url);
52+
$this->assertStringContainsString('custom=login', $with);
53+
}
54+
55+
public function test_calculate_secret_from_date_valid_and_invalid(): void {
56+
$sso = SSO::get_instance();
57+
$secret = $sso->calculate_secret_from_date('2024-01-01 00:00:00');
58+
$this->assertIsString($secret);
59+
$this->assertNotEmpty($secret);
60+
61+
$this->expectException(\WP_Ultimo\SSO\Exception\SSO_Exception::class);
62+
$sso->calculate_secret_from_date('not-a-date');
63+
}
64+
65+
public function test_get_broker_by_id_roundtrip_domains_and_secret(): void {
66+
$blog_id = 1;
67+
if (! get_site($blog_id)) {
68+
$this->markTestSkipped('Main site not available in this environment.');
69+
}
70+
switch_to_blog($blog_id);
71+
try {
72+
$sso = SSO::get_instance();
73+
$salt = $sso->salt();
74+
$coded = $sso->encode($blog_id, $salt);
75+
76+
$info = $sso->get_broker_by_id($coded);
77+
} finally {
78+
restore_current_blog();
79+
}
80+
$this->assertIsArray($info);
81+
$this->assertArrayHasKey('secret', $info);
82+
$this->assertArrayHasKey('domains', $info);
83+
$this->assertNotEmpty($info['domains']);
84+
85+
// Invalid should return null
86+
$this->assertNull($sso->get_broker_by_id('invalid'));
87+
}
88+
89+
public function test_get_final_return_url_builds_login_url_with_done_and_redirect(): void {
90+
$sso = SSO::get_instance();
91+
$base = network_home_url('/some/path');
92+
$url = add_query_arg(
93+
[
94+
$sso->get_url_path() => 'login',
95+
'redirect_to' => rawurlencode('https://example.com/after'),
96+
],
97+
$base
98+
);
99+
100+
$final = $sso->get_final_return_url($url);
101+
$this->assertStringContainsString($sso->get_url_path() . '=done', $final);
102+
$this->assertStringContainsString('redirect_to=', $final);
103+
$this->assertStringContainsString('wp-login.php', $final);
104+
}
105+
106+
public function test_broker_attach_url_contains_token_broker_checksum_and_params(): void {
107+
// Work on a dedicated site to avoid pollution from other tests.
108+
$blog_id = 1;
109+
switch_to_blog($blog_id);
110+
try {
111+
$sso = SSO::get_instance();
112+
// Build a broker like SSO::get_broker but with absolute URL and in-memory state.
113+
$blog = get_blog_details($blog_id);
114+
$date = $blog ? $blog->registered : '2024-01-01 00:00:00';
115+
$secret = $sso->calculate_secret_from_date($date);
116+
$url = trailingslashit(network_home_url()) . $sso->get_url_path('grant');
117+
$broker_id = $sso->encode($blog_id, $sso->salt());
118+
$broker = new SSO_Broker($url, $broker_id, $secret);
119+
} finally {
120+
restore_current_blog();
121+
}
122+
// Avoid headers by storing token/verify in memory rather than cookies.
123+
$broker = $broker->withTokenIn(new \ArrayObject());
124+
125+
$attach = $broker->getAttachUrl(['return_url' => 'https://example.com/here']);
126+
$parts = wp_parse_url($attach);
127+
parse_str($parts['query'] ?? '', $q);
128+
129+
$this->assertArrayHasKey('broker', $q);
130+
$this->assertArrayHasKey('token', $q);
131+
$this->assertArrayHasKey('checksum', $q);
132+
$this->assertSame('https://example.com/here', $q['return_url']);
133+
134+
// JSONP variant
135+
$attach2 = $broker->getAttachUrl(['_jsonp' => '1']);
136+
$parts2 = wp_parse_url($attach2);
137+
parse_str($parts2['query'] ?? '', $q2);
138+
$this->assertSame('1', $q2['_jsonp']);
139+
}
140+
141+
public function test_session_handler_start_throws_when_not_logged_in(): void {
142+
$this->expectException(SSO_Session_Exception::class);
143+
$this->expectExceptionCode(401);
144+
145+
$sso = SSO::get_instance();
146+
unset($_REQUEST['broker']);
147+
$handler = new \WP_Ultimo\SSO\SSO_Session_Handler($sso);
148+
$handler->start();
149+
}
150+
151+
public function test_get_return_type_defaults_and_values(): void {
152+
$sso = SSO::get_instance();
153+
154+
unset($_REQUEST['return_type']);
155+
$this->assertSame('redirect', $sso->get_return_type());
156+
157+
$_REQUEST['return_type'] = 'jsonp';
158+
$this->assertSame('jsonp', $sso->get_return_type());
159+
160+
$_REQUEST['return_type'] = 'json';
161+
$this->assertSame('json', $sso->get_return_type());
162+
163+
$_REQUEST['return_type'] = 'invalid';
164+
$this->assertSame('redirect', $sso->get_return_type());
165+
}
166+
}

tests/unit/SSO_Test.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
use WP_Ultimo\SSO\SSO;
4+
use WP_Ultimo\SSO\SSO_Session_Handler;
5+
6+
/**
7+
* SSO unit tests covering helpers and session handler.
8+
*/
9+
class SSO_Test extends \WP_UnitTestCase {
10+
11+
public function setUp(): void {
12+
parent::setUp();
13+
// Ensure SSO singleton is initialized fresh per test when needed.
14+
// SSO hooks only run if enabled; our tests use direct methods.
15+
}
16+
17+
public function tearDown(): void {
18+
// Remove any filters set in tests.
19+
remove_all_filters('wu_sso_enabled');
20+
remove_all_filters('wu_sso_get_url_path');
21+
remove_all_filters('determine_current_user');
22+
parent::tearDown();
23+
}
24+
25+
public function test_with_sso_appends_query_param_when_enabled(): void {
26+
add_filter('wu_sso_enabled', '__return_true');
27+
$url = 'https://example.com/path?foo=bar';
28+
$withSso = SSO::with_sso($url);
29+
30+
$this->assertStringContainsString('sso=login', $withSso, 'SSO query arg should be added');
31+
$this->assertStringContainsString('foo=bar', $withSso, 'Original query args should be preserved');
32+
}
33+
34+
public function test_with_sso_returns_same_url_when_disabled(): void {
35+
add_filter('wu_sso_enabled', '__return_false');
36+
$url = 'https://example.com/path?foo=bar';
37+
$withSso = SSO::with_sso($url);
38+
39+
$this->assertSame($url, $withSso, 'URL should be unchanged when SSO disabled');
40+
}
41+
42+
public function test_encode_decode_roundtrip_uses_hashids(): void {
43+
$sso = SSO::get_instance();
44+
$salt = $sso->salt();
45+
$value = 12345;
46+
47+
$encoded = $sso->encode($value, $salt);
48+
$this->assertIsString($encoded);
49+
$this->assertNotSame((string) $value, $encoded, 'Encoded value should be obfuscated');
50+
51+
$decoded = $sso->decode($encoded, $salt);
52+
$this->assertSame($value, $decoded, 'Decoded value should match original');
53+
}
54+
55+
public function test_session_handler_start_and_resume_sets_target_user_id(): void {
56+
// Create a user and ensure WP recognizes it as current.
57+
$user_id = self::factory()->user->create();
58+
if (! $user_id) {
59+
// Fallback to default admin user often present in WP tests.
60+
$user_id = 1;
61+
}
62+
63+
// Ensure we have a site id to encode as broker id.
64+
$site_id = get_current_blog_id();
65+
$sso = SSO::get_instance();
66+
$salt = $sso->salt();
67+
$broker = $sso->encode($site_id, $salt);
68+
69+
// Simulate request param that SSO_Session_Handler reads.
70+
$_REQUEST['broker'] = $broker;
71+
72+
$handler = new SSO_Session_Handler($sso);
73+
74+
// Simulate the broker session storage that start() would create.
75+
set_site_transient("sso-{$broker}-{$site_id}", $user_id, 180);
76+
77+
// resume() should read the transient and set target user id inside SSO.
78+
$handler->resume($broker);
79+
80+
$this->assertSame($user_id, $sso->get_target_user_id(), 'Target user id should be set from session');
81+
}
82+
}

0 commit comments

Comments
 (0)