Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add RelayListMetadataTest
  • Loading branch information
Sebastix committed May 28, 2025
commit 6b529752491a13da97fa60d26cf39b0904aaa03b
11 changes: 11 additions & 0 deletions src/Event/List/RelayListMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public function getRelays(): array
if (empty($this->relays)) {
throw new \RuntimeException('The relays property is empty of ' . __CLASS__);
}
foreach ($this->relays as $relay) {
if (str_starts_with($relay[1], 'wss://') === false) {
throw new \RuntimeException('The URL ' . $relay[1] . ' is not a valid websocket URL');
}
}
return $this->relays;
}

Expand All @@ -70,6 +75,9 @@ public function getWriteRelays(): array
}
$writeRelays = [];
foreach ($this->relays as $relay) {
if (str_starts_with($relay[1], 'wss://') === false) {
throw new \RuntimeException('The URL ' . $relay[1] . ' is not a valid websocket URL');
}
if (!isset($relay[2]) && str_starts_with($relay[1], 'wss://')) {
$writeRelays[] = $relay[1];
}
Expand All @@ -92,6 +100,9 @@ public function getReadRelays(): array
}
$readRelays = [];
foreach ($this->relays as $relay) {
if (str_starts_with($relay[1], 'wss://') === false) {
throw new \RuntimeException('The URL ' . $relay[1] . ' is not a valid websocket URL');
}
if (!isset($relay[2]) && str_starts_with($relay[1], 'wss://')) {
$readRelays[] = $relay[1];
}
Expand Down
177 changes: 177 additions & 0 deletions tests/RelayListMetadataTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use swentel\nostr\Event\List\RelayListMetadata;
use swentel\nostr\Relay\Relay;
use swentel\nostr\RelayResponse\RelayResponseEvent;
use swentel\nostr\Request\Request;

class RelayListMetadataTest extends TestCase
{
private const TEST_PUBKEY = '884704bd421721e292edbec8466287dd3a3c834c2ed269822b95a85e4f8a0c47';
private const TEST_RELAY_URL = 'wss://purplepag.es';

/**
* Test that the kind number cannot be changed from 10002.
*/
public function testKindNumberIsFixed(): void
{
$relayList = new RelayListMetadata(self::TEST_PUBKEY);
$this->assertEquals(10002, $relayList->getKind());
}

/**
* Test fetching relay list with empty response.
*/
public function testEmptyRelayList(): void
{
$mockRelay = $this->createMock(Relay::class);
$mockRequest = $this->createMock(Request::class);
$mockRequest->method('send')->willReturn([]);

// Replace the Request constructor with our mock
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('The relays property is empty of swentel\nostr\Event\List\RelayListMetadata');

$relayList = new RelayListMetadata(self::TEST_PUBKEY);
$relayList->getRelays();
}

/**
* Test getting write relays.
*/
public function testGetWriteRelays(): void
{
$relayList = $this->createRelayListWithMockData([
['r', 'wss://relay1.com', 'write'],
['r', 'wss://relay2.com', 'read'],
['r', 'wss://relay3.com'], // Both read and write
['r', 'wss://relay4.com', 'write'],
]);

$writeRelays = $relayList->getWriteRelays();

$this->assertContains('wss://relay1.com', $writeRelays);
$this->assertNotContains('wss://relay2.com', $writeRelays);
$this->assertContains('wss://relay3.com', $writeRelays);
$this->assertContains('wss://relay4.com', $writeRelays);
$this->assertNotContains('http://invalid.com', $writeRelays);
}

/**
* Test getting read relays.
*/
public function testGetReadRelays(): void
{
$relayList = $this->createRelayListWithMockData([
['r', 'wss://relay1.com', 'read'],
['r', 'wss://relay2.com', 'write'],
['r', 'wss://relay3.com'], // Both read and write
['r', 'wss://relay4.com', 'read'],
]);

$readRelays = $relayList->getReadRelays();

$this->assertContains('wss://relay1.com', $readRelays);
$this->assertNotContains('wss://relay2.com', $readRelays);
$this->assertContains('wss://relay3.com', $readRelays);
$this->assertContains('wss://relay4.com', $readRelays);
$this->assertNotContains('http://invalid.com', $readRelays);
}

/**
* Test that fallback relays are queried when primary relay returns no results.
*/
public function testFallbackRelayQuerying(): void
{
$relayList = $this->createRelayListWithMockData(
[['r', 'wss://fallback-relay.com', 'write']],
true
);

$writeRelays = $relayList->getWriteRelays();
$this->assertContains('wss://fallback-relay.com', $writeRelays);
}

/**
* Test handling of malformed relay URLs.
*/
public function testMalformedRelayUrls(): void
{
$relayList = $this->createRelayListWithMockData([
['r', 'not-a-url', 'write'],
['r', 'http://not-secure.com', 'write'],
['r', 'wss://valid.com', 'write'],
]);

$this->expectException(\RuntimeException::class);
$relayList->getWriteRelays();

}

/**
* Creates a RelayListMetadata instance with mock data.
*/
private function createRelayListWithMockData(array $tags, bool $useFallback = false): RelayListMetadata
{
// Create a mock event response
$mockEvent = new \stdClass();
$mockEvent->tags = $tags;

// Create a mock relay response
$mockRelayResponse = $this->createMock(RelayResponseEvent::class);
$mockRelayResponse->event = $mockEvent;

// Create reflection class to modify private properties
$relayList = new RelayListMetadata(self::TEST_PUBKEY);
$reflection = new \ReflectionClass($relayList);

$relaysProperty = $reflection->getProperty('relays');
$relaysProperty->setAccessible(true);
$relaysProperty->setValue($relayList, $tags);

return $relayList;
}

/**
* Test that getKnownRelays returns expected relays.
*/
public function testGetKnownRelays(): void
{
$relayList = new RelayListMetadata(self::TEST_PUBKEY);
$reflection = new \ReflectionClass($relayList);

$method = $reflection->getMethod('getKnownRelays');
$method->setAccessible(true);

$knownRelays = $method->invoke($relayList);

$this->assertIsArray($knownRelays);
$this->assertNotEmpty($knownRelays);
foreach ($knownRelays as $relay) {
$this->assertStringStartsWith('wss://', $relay);
}
}

/**
* Test that empty relays throw exception for all getter methods.
*/
public function testEmptyRelaysThrowExceptions(): void
{
$relayList = new RelayListMetadata(self::TEST_PUBKEY);
$reflection = new \ReflectionClass($relayList);

$relaysProperty = $reflection->getProperty('relays');
$relaysProperty->setAccessible(true);
$relaysProperty->setValue($relayList, []);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('The relays property is empty of swentel\nostr\Event\List\RelayListMetadata');

$relayList->getRelays();
$relayList->getReadRelays();
$relayList->getWriteRelays();
}
}
Loading