Skip to content
Merged
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
12 changes: 10 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache">
cacheDirectory=".phpunit.cache"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true">
<testsuites>
<testsuite name="Unit">
<directory>tests</directory>
Expand All @@ -13,11 +17,15 @@
<include>
<directory>src</directory>
</include>
<exclude>
<directory>src/autoload.php</directory>
</exclude>
</source>
<coverage>
<report>
<html outputDirectory="coverage/html"/>
<text outputFile="php://stdout"/>
<text outputFile="php://stdout" showUncoveredFiles="true"/>
<clover outputFile="coverage/clover.xml"/>
</report>
</coverage>
</phpunit>
13 changes: 13 additions & 0 deletions src/Types/U64.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,17 @@ public function getTypeName(): string
{
return 'U64';
}

public function decode(\Substrate\ScaleCodec\Bytes\ScaleBytes $bytes): string|int
{
$data = array_reverse($bytes->readBytes(8)); // Reverse bytes for little-endian
$value = '0';

foreach ($data as $byte) {
$value = bcmul($value, '256');
$value = bcadd($value, (string)$byte);
}

return $value;
}
}
209 changes: 209 additions & 0 deletions tests/Bytes/ScaleBytesComprehensiveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<?php

declare(strict_types=1);

namespace Substrate\ScaleCodec\Tests\Bytes;

use PHPUnit\Framework\TestCase;
use Substrate\ScaleCodec\Bytes\ScaleBytes;
use InvalidArgumentException;

class ScaleBytesComprehensiveTest extends TestCase
{
// ==================== Creation Tests ====================

public function testCreateFromHexWithPrefix(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals([1, 2, 3], $bytes->toBytes());
}

public function testCreateFromHexWithoutPrefix(): void
{
$bytes = ScaleBytes::fromHex('010203');
$this->assertEquals([1, 2, 3], $bytes->toBytes());
}

public function testCreateFromBytes(): void
{
$bytes = ScaleBytes::fromBytes([1, 2, 3]);
$this->assertEquals([1, 2, 3], $bytes->toBytes());
}

public function testCreateEmpty(): void
{
$bytes = ScaleBytes::empty();
$this->assertEquals([], $bytes->toBytes());
$this->assertEquals(0, $bytes->length());
}

public function testInvalidHexThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
ScaleBytes::fromHex('invalid');
}

// ==================== Read Tests ====================

public function testReadByte(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals(1, $bytes->readByte());
$this->assertEquals(2, $bytes->readByte());
$this->assertEquals(3, $bytes->readByte());
}

public function testReadBytes(): void
{
$bytes = ScaleBytes::fromHex('0x0102030405');
$this->assertEquals([1, 2], $bytes->readBytes(2));
$this->assertEquals([3, 4, 5], $bytes->readBytes(3));
}

public function testPeekByte(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals(1, $bytes->peekByte());
$this->assertEquals(1, $bytes->readByte()); // Offset not moved by peek
}

public function testPeekBytes(): void
{
$bytes = ScaleBytes::fromHex('0x0102030405');
$this->assertEquals([1, 2, 3], $bytes->peekBytes(3));
$this->assertEquals(1, $bytes->readByte()); // Offset not moved by peek
}

public function testReadBeyondEndThrowsException(): void
{
$this->expectException(\RuntimeException::class);
$bytes = ScaleBytes::fromHex('0x0102');
$bytes->readBytes(3);
}

// ==================== State Tests ====================

public function testRemaining(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals(3, $bytes->remaining());
$bytes->readByte();
$this->assertEquals(2, $bytes->remaining());
}

public function testLength(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals(3, $bytes->length());
}

public function testHasRemaining(): void
{
$bytes = ScaleBytes::fromHex('0x0102');
$this->assertTrue($bytes->hasRemaining());
$bytes->readBytes(2);
$this->assertFalse($bytes->hasRemaining());
}

public function testIsExhausted(): void
{
$bytes = ScaleBytes::fromHex('0x0102');
$this->assertFalse($bytes->isExhausted());
$bytes->readBytes(2);
$this->assertTrue($bytes->isExhausted());
}

public function testGetOffset(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$this->assertEquals(0, $bytes->getOffset());
$bytes->readByte();
$this->assertEquals(1, $bytes->getOffset());
}

// ==================== Manipulation Tests ====================

public function testConcat(): void
{
$bytes1 = ScaleBytes::fromHex('0x0102');
$bytes2 = ScaleBytes::fromHex('0x0304');
$result = $bytes1->concat($bytes2);
$this->assertEquals([1, 2, 3, 4], $result->toBytes());
}

public function testConcatEmpty(): void
{
$bytes = ScaleBytes::fromHex('0x0102');
$empty = ScaleBytes::empty();
$result = $bytes->concat($empty);
$this->assertEquals([1, 2], $result->toBytes());
}

public function testSlice(): void
{
$bytes = ScaleBytes::fromHex('0x0102030405');
$slice = $bytes->slice(1, 3);
$this->assertEquals([2, 3, 4], $slice->toBytes());
}

public function testSliceToEnd(): void
{
$bytes = ScaleBytes::fromHex('0x0102030405');
$slice = $bytes->slice(2);
$this->assertEquals([3, 4, 5], $slice->toBytes());
}

public function testReset(): void
{
$bytes = ScaleBytes::fromHex('0x010203');
$bytes->readBytes(2);
$this->assertEquals(2, $bytes->getOffset());
$bytes->reset();
$this->assertEquals(0, $bytes->getOffset());
}

// ==================== Output Tests ====================

public function testToHex(): void
{
$bytes = ScaleBytes::fromBytes([1, 2, 3]);
$this->assertEquals('0x010203', $bytes->toHex());
$this->assertEquals('010203', $bytes->toHex(false));
}

public function testToString(): void
{
$bytes = ScaleBytes::fromBytes([1, 2, 3]);
$this->assertEquals('0x010203', (string) $bytes);
}

// ==================== Edge Case Tests ====================

public function testEmptyBytes(): void
{
$bytes = ScaleBytes::empty();
$this->assertEquals(0, $bytes->length());
$this->assertEquals(0, $bytes->remaining());
$this->assertEquals('0x', $bytes->toHex());
}

public function testLargeByteArray(): void
{
$data = range(0, 255);
$bytes = ScaleBytes::fromBytes($data);
$this->assertEquals(256, $bytes->length());
$this->assertEquals($data, $bytes->toBytes());
}

public function testAllZeroBytes(): void
{
$bytes = ScaleBytes::fromHex('0x000000');
$this->assertEquals([0, 0, 0], $bytes->toBytes());
}

public function testAllMaxBytes(): void
{
$bytes = ScaleBytes::fromHex('0xffffff');
$this->assertEquals([255, 255, 255], $bytes->toBytes());
}
}
59 changes: 59 additions & 0 deletions tests/Exception/ExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Substrate\ScaleCodec\Tests\Exception;

use PHPUnit\Framework\TestCase;
use Substrate\ScaleCodec\Exception\{ScaleEncodeException, ScaleDecodeException, InvalidTypeException};

class ExceptionTest extends TestCase
{
// ==================== ScaleEncodeException Tests ====================

public function testInvalidTypeException(): void
{
$exception = ScaleEncodeException::invalidType('U8', 'string');
$this->assertInstanceOf(ScaleEncodeException::class, $exception);
$this->assertStringContainsString('U8', $exception->getMessage());
}

public function testOutOfRangeException(): void
{
$exception = ScaleEncodeException::outOfRange('U8', 300, '0-255');
$this->assertInstanceOf(ScaleEncodeException::class, $exception);
$this->assertStringContainsString('300', $exception->getMessage());
}

// ==================== ScaleDecodeException Tests ====================

public function testInvalidBoolValue(): void
{
$exception = ScaleDecodeException::invalidBoolValue(5);
$this->assertInstanceOf(ScaleDecodeException::class, $exception);
$this->assertStringContainsString('5', $exception->getMessage());
}

public function testInvalidEnumVariant(): void
{
$exception = ScaleDecodeException::invalidEnumVariant(10, [0, 1, 2]);
$this->assertInstanceOf(ScaleDecodeException::class, $exception);
$this->assertStringContainsString('10', $exception->getMessage());
}

// ==================== InvalidTypeException Tests ====================

public function testNotRegistered(): void
{
$exception = InvalidTypeException::notRegistered('CustomType');
$this->assertInstanceOf(InvalidTypeException::class, $exception);
$this->assertStringContainsString('CustomType', $exception->getMessage());
}

public function testInvalidFormat(): void
{
$exception = InvalidTypeException::invalidFormat('Vec', 'Expected type parameter');
$this->assertInstanceOf(InvalidTypeException::class, $exception);
$this->assertStringContainsString('Vec', $exception->getMessage());
}
}
Loading
Loading