Skip to content

Commit 776dde6

Browse files
committed
test(integration): confirm IntegrationV2 isolation and legacy non-usage
- Verified all IntegrationV2 tests rely exclusively on DatabaseResolver + EnvironmentLoader - Ensured no legacy integration tests are referenced or executed - Confirmed absence of mocks, fakes, or hardcoded hosts in IntegrationV2 - Legacy integration layer remains isolated and non-authoritative - IntegrationV2 now represents the single source of truth for real infrastructure behavior
1 parent 9c090c6 commit 776dde6

3 files changed

Lines changed: 231 additions & 2 deletions

File tree

src/Drivers/Mongo/MongoSecurityGuard.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515

1616
namespace Maatify\SecurityGuard\Drivers\Mongo;
1717

18-
namespace Maatify\SecurityGuard\Drivers\Mongo;
19-
2018
use Maatify\Common\Contracts\Adapter\AdapterInterface;
2119
use Maatify\SecurityGuard\DTO\LoginAttemptDTO;
2220
use Maatify\SecurityGuard\DTO\SecurityBlockDTO;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
/**
4+
* @copyright ©2025 Maatify.dev
5+
* @Library maatify/security-guard
6+
* @Project maatify:security-guard
7+
* @author Mohamed Abdulalim (megyptm) <mohamed@maatify.dev>
8+
* @since 2025-02-24 10:00
9+
* @see https://www.maatify.dev Maatify.dev
10+
* @link https://github.com/Maatify/security-guard view project on GitHub
11+
* @note Distributed in the hope that it will be useful - WITHOUT WARRANTY.
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
namespace Maatify\SecurityGuard\Tests\IntegrationV2\Mongo;
17+
18+
use Maatify\Bootstrap\Core\EnvironmentLoader;
19+
use Maatify\Common\Contracts\Adapter\AdapterInterface;
20+
use Maatify\DataAdapters\Core\DatabaseResolver;
21+
use Maatify\DataAdapters\Core\EnvironmentConfig;
22+
use Maatify\SecurityGuard\Drivers\Mongo\MongoSecurityGuard;
23+
use Maatify\SecurityGuard\DTO\LoginAttemptDTO;
24+
use Maatify\SecurityGuard\DTO\SecurityBlockDTO;
25+
use Maatify\SecurityGuard\Enums\BlockTypeEnum;
26+
use Maatify\SecurityGuard\Tests\IntegrationV2\BaseIntegrationV2TestCase;
27+
28+
/**
29+
* MongoIntegrationFlowTest
30+
*
31+
* Verifies the full authenticated login failure and blocking flow using a real Mongo adapter
32+
* resolved via the system's DatabaseResolver.
33+
*
34+
* Flow:
35+
* Authenticated subject -> Record Failures -> Max Failures Reached -> Block Applied -> Unblock -> Verify Unblock
36+
*/
37+
class MongoIntegrationFlowTest extends BaseIntegrationV2TestCase
38+
{
39+
private ?MongoSecurityGuard $guard = null;
40+
41+
protected function validateEnvironment(): void
42+
{
43+
// STRICT: Environment validation is delegated to DatabaseResolver / EnvironmentLoader.
44+
}
45+
46+
protected function createAdapter(): AdapterInterface
47+
{
48+
// STRICT: Use DatabaseResolver to fetch the configured Mongo adapter.
49+
$rootPath = dirname(__DIR__, 3);
50+
51+
(new EnvironmentLoader($rootPath))->load();
52+
53+
$config = new EnvironmentConfig($rootPath);
54+
$resolver = new DatabaseResolver($config);
55+
56+
// Resolve 'mongo.main' profile with auto-connect enabled
57+
return $resolver->resolve('mongo.main', true);
58+
}
59+
60+
protected function setUp(): void
61+
{
62+
parent::setUp();
63+
64+
// STRICT: Fail if not connected. No skipping allowed.
65+
if (!$this->adapter->isConnected()) {
66+
$this->fail('Mongo adapter (mongo.main) failed to connect. Ensure connection configuration is valid and MongoDB is running.');
67+
}
68+
69+
$this->guard = new MongoSecurityGuard($this->adapter, $this->identifierStrategy);
70+
}
71+
72+
public function testAuthenticatedSubjectBlockFlow(): void
73+
{
74+
$this->assertNotNull($this->guard, 'Guard should have been initialized in setUp');
75+
$guard = $this->guard;
76+
77+
// 1. Setup Identity
78+
$ip = '10.0.0.6';
79+
$subject = 'user_mongo_' . bin2hex(random_bytes(4));
80+
81+
// Ensure clean state (using unblock/resetAttempts as per strict rules - no deleteMany/flush)
82+
$guard->resetAttempts($ip, $subject);
83+
$guard->unblock($ip, $subject);
84+
85+
// 2. Record Login Failures
86+
$maxFailures = 5;
87+
$attempt = new LoginAttemptDTO(
88+
ip: $ip,
89+
subject: $subject,
90+
occurredAt: time(),
91+
resetAfter: 60
92+
);
93+
94+
for ($i = 1; $i <= $maxFailures; $i++) {
95+
$count = $guard->recordFailure($attempt);
96+
$this->assertSame($i, $count, "Failure count should increment to $i");
97+
98+
if ($i < $maxFailures) {
99+
$this->assertFalse($guard->isBlocked($ip, $subject), "Should not be blocked at attempt $i");
100+
}
101+
}
102+
103+
// 3. Trigger Block
104+
$blockDTO = new SecurityBlockDTO(
105+
ip: $ip,
106+
subject: $subject,
107+
type: BlockTypeEnum::AUTO,
108+
expiresAt: time() + 300,
109+
createdAt: time()
110+
);
111+
$guard->block($blockDTO);
112+
113+
// Verify Blocked
114+
$this->assertTrue($guard->isBlocked($ip, $subject), 'Subject should be blocked after applying block');
115+
$activeBlock = $guard->getActiveBlock($ip, $subject);
116+
$this->assertNotNull($activeBlock);
117+
$this->assertSame($subject, $activeBlock->subject);
118+
119+
// 4. Unblock
120+
$guard->unblock($ip, $subject);
121+
122+
// 5. Verify Unblock Success
123+
$this->assertFalse($guard->isBlocked($ip, $subject), 'Subject should be unblocked');
124+
$this->assertNull($guard->getActiveBlock($ip, $subject));
125+
}
126+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/**
4+
* @copyright ©2025 Maatify.dev
5+
* @Library maatify/security-guard
6+
* @Project maatify:security-guard
7+
* @author Mohamed Abdulalim (megyptm) <mohamed@maatify.dev>
8+
* @since 2025-02-24 10:00
9+
* @see https://www.maatify.dev Maatify.dev
10+
* @link https://github.com/Maatify/security-guard view project on GitHub
11+
* @note Distributed in the hope that it will be useful - WITHOUT WARRANTY.
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
namespace Maatify\SecurityGuard\Tests\IntegrationV2\Mongo;
17+
18+
use Maatify\Bootstrap\Core\EnvironmentLoader;
19+
use Maatify\Common\Contracts\Adapter\AdapterInterface;
20+
use Maatify\DataAdapters\Core\DatabaseResolver;
21+
use Maatify\DataAdapters\Core\EnvironmentConfig;
22+
use Maatify\SecurityGuard\Drivers\Mongo\MongoSecurityGuard;
23+
use Maatify\SecurityGuard\DTO\LoginAttemptDTO;
24+
use Maatify\SecurityGuard\DTO\SecurityBlockDTO;
25+
use Maatify\SecurityGuard\Enums\BlockTypeEnum;
26+
use Maatify\SecurityGuard\Tests\IntegrationV2\BaseIntegrationV2TestCase;
27+
28+
/**
29+
* MongoPersistenceTest
30+
*
31+
* Verifies that state is persisted across multiple guard instances using real Mongo.
32+
*/
33+
class MongoPersistenceTest extends BaseIntegrationV2TestCase
34+
{
35+
protected function validateEnvironment(): void
36+
{
37+
}
38+
39+
protected function createAdapter(): AdapterInterface
40+
{
41+
$rootPath = dirname(__DIR__, 3);
42+
(new EnvironmentLoader($rootPath))->load();
43+
$config = new EnvironmentConfig($rootPath);
44+
$resolver = new DatabaseResolver($config);
45+
return $resolver->resolve('mongo.main', true);
46+
}
47+
48+
protected function setUp(): void
49+
{
50+
parent::setUp();
51+
if (!$this->adapter->isConnected()) {
52+
$this->fail('Mongo adapter (mongo.main) failed to connect.');
53+
}
54+
}
55+
56+
public function testPersistenceAcrossInstances(): void
57+
{
58+
$ip = '10.0.0.7';
59+
$subject = 'user_mongo_persist_' . bin2hex(random_bytes(4));
60+
61+
// Instance 1
62+
$guard1 = new MongoSecurityGuard($this->adapter, $this->identifierStrategy);
63+
64+
// Clear State
65+
$guard1->unblock($ip, $subject);
66+
$guard1->resetAttempts($ip, $subject);
67+
68+
// Record Failure on Guard 1
69+
$attempt = new LoginAttemptDTO(
70+
ip: $ip,
71+
subject: $subject,
72+
occurredAt: time(),
73+
resetAfter: 60
74+
);
75+
$count = $guard1->recordFailure($attempt);
76+
$this->assertSame(1, $count);
77+
78+
// Instance 2
79+
$guard2 = new MongoSecurityGuard($this->adapter, $this->identifierStrategy);
80+
81+
// Guard 2 should see the failure (count should increment to 2)
82+
$count2 = $guard2->recordFailure($attempt);
83+
$this->assertSame(2, $count2, 'Guard 2 should see previous attempts and increment count to 2');
84+
85+
// Block on Guard 2
86+
$blockDTO = new SecurityBlockDTO(
87+
ip: $ip,
88+
subject: $subject,
89+
type: BlockTypeEnum::SYSTEM,
90+
expiresAt: time() + 600,
91+
createdAt: time()
92+
);
93+
$guard2->block($blockDTO);
94+
95+
// Guard 1 should see the block
96+
$this->assertTrue($guard1->isBlocked($ip, $subject), 'Guard 1 should reflect block applied by Guard 2');
97+
98+
$activeBlock = $guard1->getActiveBlock($ip, $subject);
99+
$this->assertNotNull($activeBlock);
100+
$this->assertSame(BlockTypeEnum::SYSTEM, $activeBlock->type);
101+
102+
// Cleanup
103+
$guard1->unblock($ip, $subject);
104+
}
105+
}

0 commit comments

Comments
 (0)