diff --git a/phpunit.xml b/phpunit.xml index b7dfbac..12ba1f1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -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"> tests @@ -13,11 +17,15 @@ src + + src/autoload.php + - + + diff --git a/src/Types/U64.php b/src/Types/U64.php index 96506f7..951583e 100644 --- a/src/Types/U64.php +++ b/src/Types/U64.php @@ -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; + } } diff --git a/tests/Bytes/ScaleBytesComprehensiveTest.php b/tests/Bytes/ScaleBytesComprehensiveTest.php new file mode 100644 index 0000000..780352a --- /dev/null +++ b/tests/Bytes/ScaleBytesComprehensiveTest.php @@ -0,0 +1,209 @@ +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()); + } +} diff --git a/tests/Exception/ExceptionTest.php b/tests/Exception/ExceptionTest.php new file mode 100644 index 0000000..f179f1f --- /dev/null +++ b/tests/Exception/ExceptionTest.php @@ -0,0 +1,59 @@ +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()); + } +} diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php new file mode 100644 index 0000000..060ba22 --- /dev/null +++ b/tests/Integration/IntegrationTest.php @@ -0,0 +1,160 @@ +registry = new TypeRegistry(); + $this->factory = new TypeFactory($this->registry); + } + + // ==================== Nested Type Tests ==================== + + public function testNestedVecVecU8(): void + { + $vec = new VecType($this->registry); + $innerVec = new VecType($this->registry); + $u8 = new U8($this->registry); + + $innerVec->setElementType($u8); + $vec->setElementType($innerVec); + + $data = [[1, 2], [3, 4], [5, 6]]; + $encoded = $vec->encode($data); + $decoded = $vec->decode(ScaleBytes::fromBytes($encoded->toBytes())); + + $this->assertEquals($data, $decoded); + } + + public function testOptionVecU32(): void + { + $option = new OptionType($this->registry); + $vec = new VecType($this->registry); + $u32 = new U32($this->registry); + + $vec->setElementType($u32); + $option->setInnerType($vec); + + // Test Some + $data = [100, 200, 300]; + $encoded = $option->encode($data); + $decoded = $option->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($data, $decoded); + + // Test None + $encoded = $option->encode(null); + $decoded = $option->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertNull($decoded); + } + + public function testVecOptionU8(): void + { + $vec = new VecType($this->registry); + $option = new OptionType($this->registry); + $u8 = new U8($this->registry); + + $option->setInnerType($u8); + $vec->setElementType($option); + + $data = [1, null, 3, null, 5]; + $encoded = $vec->encode($data); + $decoded = $vec->decode(ScaleBytes::fromBytes($encoded->toBytes())); + + $this->assertEquals($data, $decoded); + } + + // ==================== Complex Struct Tests ==================== + + public function testNestedStruct(): void + { + $outer = new StructType($this->registry); + $inner = new StructType($this->registry); + $u8 = new U8($this->registry); + $u32 = new U32($this->registry); + + $inner->setFields([ + 'x' => $u8, + 'y' => $u32, + ]); + + $outer->setFields([ + 'id' => $u8, + 'inner' => $inner, + ]); + + $data = [ + 'id' => 1, + 'inner' => ['x' => 10, 'y' => 1000], + ]; + + $encoded = $outer->encode($data); + $decoded = $outer->decode(ScaleBytes::fromBytes($encoded->toBytes())); + + $this->assertEquals($data, $decoded); + } + + // ==================== TypeFactory Integration Tests ==================== + + public function testFactoryCreateVecU8(): void + { + $type = $this->factory->create('Vec'); + $this->assertInstanceOf(VecType::class, $type); + } + + public function testFactoryCreateOptionU32(): void + { + $type = $this->factory->create('Option'); + $this->assertInstanceOf(OptionType::class, $type); + } + + public function testFactoryCreateNestedTypes(): void + { + $type = $this->factory->create('Vec>'); + $this->assertInstanceOf(VecType::class, $type); + } + + // ==================== Real-world Scenario Tests ==================== + + public function testBalanceTransferScenario(): void + { + // Simulate a balance transfer call + $call = new StructType($this->registry); + $compact = new Compact($this->registry); + + $call->setFields([ + 'value' => $compact, + ]); + + $data = ['value' => 1000000000]; + $encoded = $call->encode($data); + $decoded = $call->decode(ScaleBytes::fromBytes($encoded->toBytes())); + + $this->assertEquals($data, $decoded); + } + + public function testBatchTransactions(): void + { + $vec = new VecType($this->registry); + $u8 = new U8($this->registry); + + $vec->setElementType($u8); + + // Simulate batch of calls + $data = [1, 2, 3, 4, 5]; + $encoded = $vec->encode($data); + $decoded = $vec->decode(ScaleBytes::fromBytes($encoded->toBytes())); + + $this->assertEquals($data, $decoded); + } +} diff --git a/tests/Types/CompactBoundaryTest.php b/tests/Types/CompactBoundaryTest.php new file mode 100644 index 0000000..356bfea --- /dev/null +++ b/tests/Types/CompactBoundaryTest.php @@ -0,0 +1,194 @@ +registry = new TypeRegistry(); + $this->compact = new Compact($this->registry); + } + + // ==================== Single Byte Mode (0-63) ==================== + + public function testEncodeZero(): void + { + $result = $this->compact->encode(0); + $this->assertEquals('0x00', $result->toHex()); + } + + public function testEncodeOne(): void + { + $result = $this->compact->encode(1); + $this->assertEquals('0x04', $result->toHex()); + } + + public function testEncodeMaxSingleByte(): void + { + $result = $this->compact->encode(63); + $this->assertEquals('0xfc', $result->toHex()); + } + + public function testDecodeZero(): void + { + $bytes = ScaleBytes::fromHex('0x00'); + $this->assertEquals(0, $this->compact->decode($bytes)); + } + + public function testDecodeMaxSingleByte(): void + { + $bytes = ScaleBytes::fromHex('0xfc'); + $this->assertEquals(63, $this->compact->decode($bytes)); + } + + // ==================== Two Byte Mode (64-16383) ==================== + + public function testEncodeMinTwoByte(): void + { + $result = $this->compact->encode(64); + $this->assertEquals('0x0101', $result->toHex()); + } + + public function testEncodeMaxTwoByte(): void + { + $result = $this->compact->encode(16383); + $this->assertEquals(2, count($result->toBytes())); + } + + public function testDecodeMinTwoByte(): void + { + $bytes = ScaleBytes::fromHex('0x0101'); + $this->assertEquals(64, $this->compact->decode($bytes)); + } + + // ==================== Four Byte Mode (16384-1073741823) ==================== + + public function testEncodeMinFourByte(): void + { + $result = $this->compact->encode(16384); + $this->assertEquals(4, count($result->toBytes())); + } + + public function testEncodeMaxFourByte(): void + { + $result = $this->compact->encode(1073741823); + $this->assertEquals(4, count($result->toBytes())); + } + + public function testRoundTripFourByte(): void + { + $values = [16384, 1000000, 100000000, 1073741823]; + foreach ($values as $value) { + $encoded = $this->compact->encode($value); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($value, $decoded); + } + } + + // ==================== Big Integer Mode (> 1073741823) ==================== + + public function testEncodeMinBigInt(): void + { + $result = $this->compact->encode(1073741824); + $firstByte = $result->toBytes()[0]; + $this->assertEquals(0x03, $firstByte & 0x03); + } + + public function testEncodeLargeValue(): void + { + $value = '1000000000000000000'; + $result = $this->compact->encode($value); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($result->toBytes())); + $this->assertEquals($value, (string) $decoded); + } + + public function testEncodeU128Max(): void + { + $value = '340282366920938463463374607431768211455'; + $result = $this->compact->encode($value); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($result->toBytes())); + $this->assertEquals($value, (string) $decoded); + } + + // ==================== String Input Tests ==================== + + public function testEncodeStringInput(): void + { + $result = $this->compact->encode('1000'); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($result->toBytes())); + $this->assertEquals(1000, $decoded); + } + + public function testEncodeLargeStringInput(): void + { + $value = '999999999999999999999999'; + $result = $this->compact->encode($value); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($result->toBytes())); + $this->assertEquals($value, (string) $decoded); + } + + // ==================== Invalid Input Tests ==================== + + public function testEncodeNegativeThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $this->compact->encode(-1); + } + + public function testEncodeArrayThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $this->compact->encode([]); + } + + // ==================== Validation Tests ==================== + + public function testIsValidForPositiveIntegers(): void + { + $this->assertTrue($this->compact->isValid(0)); + $this->assertTrue($this->compact->isValid(100)); + $this->assertTrue($this->compact->isValid(1000000)); + $this->assertTrue($this->compact->isValid('123456789')); + } + + public function testIsValidRejectsNegative(): void + { + $this->assertFalse($this->compact->isValid(-1)); + $this->assertFalse($this->compact->isValid('-100')); + } + + public function testIsValidRejectsInvalidTypes(): void + { + $this->assertFalse($this->compact->isValid([])); + $this->assertFalse($this->compact->isValid(null)); + } + + // ==================== Round Trip Tests ==================== + + public function testRoundTripAllModes(): void + { + $values = [ + 0, 1, 63, // Single byte + 64, 1000, 16383, // Two byte + 16384, 1000000, 1073741823, // Four byte + 1073741824, 10000000000, // Big integer + ]; + + foreach ($values as $value) { + $encoded = $this->compact->encode($value); + $decoded = $this->compact->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($value, $decoded, "Round trip failed for value $value"); + } + } +} diff --git a/tests/Types/IntegerBoundaryTest.php b/tests/Types/IntegerBoundaryTest.php new file mode 100644 index 0000000..52a472f --- /dev/null +++ b/tests/Types/IntegerBoundaryTest.php @@ -0,0 +1,261 @@ +registry = new TypeRegistry(); + } + + // ==================== U8 Boundary Tests ==================== + + public function testU8MinValue(): void + { + $u8 = new U8($this->registry); + $encoded = $u8->encode(0); + $decoded = $u8->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(0, $decoded); + } + + public function testU8MaxValue(): void + { + $u8 = new U8($this->registry); + $encoded = $u8->encode(255); + $decoded = $u8->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(255, $decoded); + } + + public function testU8OverflowThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $u8 = new U8($this->registry); + $u8->encode(256); + } + + public function testU8NegativeThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $u8 = new U8($this->registry); + $u8->encode(-1); + } + + // ==================== U32 Boundary Tests ==================== + + public function testU32MinValue(): void + { + $u32 = new U32($this->registry); + $encoded = $u32->encode(0); + $decoded = $u32->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(0, $decoded); + } + + public function testU32MaxValue(): void + { + $u32 = new U32($this->registry); + $max = 4294967295; + $encoded = $u32->encode($max); + $decoded = $u32->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($max, $decoded); + } + + public function testU32OverflowThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $u32 = new U32($this->registry); + $u32->encode(4294967296); + } + + // ==================== U64 Boundary Tests ==================== + + public function testU64MinValue(): void + { + $u64 = new U64($this->registry); + $encoded = $u64->encode('0'); + $decoded = $u64->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals('0', $decoded); + } + + public function testU64MaxValue(): void + { + $u64 = new U64($this->registry); + $max = '18446744073709551615'; // As string for GMP + $encoded = $u64->encode($max); + $decoded = (string) $u64->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($max, $decoded, 'Decoded value should exactly match the encoded U64 string representation.'); + } + + public function testU64StringInput(): void + { + $u64 = new U64($this->registry); + $value = '1000000000000'; + $encoded = $u64->encode($value); + $decoded = $u64->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($value, $decoded); + } + + // ==================== U128 Boundary Tests ==================== + + public function testU128MinValue(): void + { + $u128 = new U128($this->registry); + $encoded = $u128->encode('0'); + $decoded = $u128->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals('0', $decoded); + } + + public function testU128MaxValue(): void + { + $u128 = new U128($this->registry); + $max = '340282366920938463463374607431768211455'; + $encoded = $u128->encode($max); + $decoded = $u128->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($max, $decoded); + } + + // ==================== I8 Boundary Tests ==================== + + public function testI8MinValue(): void + { + $i8 = new I8($this->registry); + $encoded = $i8->encode(-128); + $decoded = $i8->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(-128, $decoded); + } + + public function testI8MaxValue(): void + { + $i8 = new I8($this->registry); + $encoded = $i8->encode(127); + $decoded = $i8->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(127, $decoded); + } + + public function testI8Zero(): void + { + $i8 = new I8($this->registry); + $encoded = $i8->encode(0); + $decoded = $i8->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(0, $decoded); + } + + public function testI8OverflowThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $i8 = new I8($this->registry); + $i8->encode(128); + } + + public function testI8UnderflowThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $i8 = new I8($this->registry); + $i8->encode(-129); + } + + // ==================== I32 Boundary Tests ==================== + + public function testI32MinValue(): void + { + $i32 = new I32($this->registry); + $encoded = $i32->encode(-2147483648); + $decoded = $i32->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(-2147483648, $decoded); + } + + public function testI32MaxValue(): void + { + $i32 = new I32($this->registry); + $encoded = $i32->encode(2147483647); + $decoded = $i32->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals(2147483647, $decoded); + } + + // ==================== I64 Boundary Tests ==================== + + public function testI64MinValue(): void + { + $i64 = new I64($this->registry); + $min = '-9223372036854775808'; + $encoded = $i64->encode($min); + $decoded = $i64->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($min, $decoded); + } + + public function testI64MaxValue(): void + { + $i64 = new I64($this->registry); + $max = '9223372036854775807'; + $encoded = $i64->encode($max); + $decoded = $i64->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($max, $decoded); + } + + // ==================== I128 Boundary Tests ==================== + + public function testI128MinValue(): void + { + $i128 = new I128($this->registry); + $min = '-170141183460469231731687303715884105728'; + $encoded = $i128->encode($min); + $decoded = $i128->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($min, $decoded); + } + + public function testI128MaxValue(): void + { + $i128 = new I128($this->registry); + $max = '170141183460469231731687303715884105727'; + $encoded = $i128->encode($max); + $decoded = $i128->decode(ScaleBytes::fromBytes($encoded->toBytes())); + $this->assertEquals($max, $decoded); + } + + // ==================== Invalid Input Tests ==================== + + public function testInvalidTypeThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $u8 = new U8($this->registry); + $u8->encode([]); + } + + public function testNullThrowsException(): void + { + $this->expectException(ScaleEncodeException::class); + $u8 = new U8($this->registry); + $u8->encode(null); + } + + // ==================== Validation Tests ==================== + + public function testIsValidForU8(): void + { + $u8 = new U8($this->registry); + $this->assertTrue($u8->isValid(0)); + $this->assertTrue($u8->isValid(255)); + $this->assertFalse($u8->isValid(-1)); + $this->assertFalse($u8->isValid(256)); + } + + public function testIsValidForI8(): void + { + $i8 = new I8($this->registry); + $this->assertTrue($i8->isValid(-128)); + $this->assertTrue($i8->isValid(127)); + $this->assertTrue($i8->isValid(0)); + $this->assertFalse($i8->isValid(-129)); + $this->assertFalse($i8->isValid(128)); + } +} diff --git a/tests/Types/TypeFactoryComprehensiveTest.php b/tests/Types/TypeFactoryComprehensiveTest.php new file mode 100644 index 0000000..03449fe --- /dev/null +++ b/tests/Types/TypeFactoryComprehensiveTest.php @@ -0,0 +1,195 @@ +registry = new TypeRegistry(); + $this->factory = new TypeFactory($this->registry); + } + + // ==================== Simple Type Creation Tests ==================== + + public function testCreateU8(): void + { + $type = $this->factory->create('U8'); + $this->assertInstanceOf(U8::class, $type); + } + + public function testCreateU32(): void + { + $type = $this->factory->create('U32'); + $this->assertInstanceOf(U32::class, $type); + } + + public function testCreateCaseInsensitive(): void + { + $type1 = $this->factory->create('u8'); + $type2 = $this->factory->create('U8'); + $type3 = $this->factory->create('U8'); + + $this->assertInstanceOf(U8::class, $type1); + $this->assertInstanceOf(U8::class, $type2); + $this->assertInstanceOf(U8::class, $type3); + } + + // ==================== Parameterized Type Tests ==================== + + public function testCreateVecU8(): void + { + $type = $this->factory->create('Vec'); + $this->assertInstanceOf(VecType::class, $type); + } + + public function testCreateVecU32(): void + { + $type = $this->factory->create('Vec'); + $this->assertInstanceOf(VecType::class, $type); + } + + public function testCreateOptionU8(): void + { + $type = $this->factory->create('Option'); + $this->assertInstanceOf(OptionType::class, $type); + } + + public function testCreateNestedVecOption(): void + { + $type = $this->factory->create('Vec>'); + $this->assertInstanceOf(VecType::class, $type); + } + + public function testCreateOptionVec(): void + { + $type = $this->factory->create('Option>'); + $this->assertInstanceOf(OptionType::class, $type); + } + + // ==================== Tuple Type Tests ==================== + + public function testCreateEmptyTuple(): void + { + $type = $this->factory->create('()'); + // Empty tuple should be Null + $this->assertNotNull($type); + } + + public function testCreateSingleElementTuple(): void + { + $type = $this->factory->create('(U8)'); + $this->assertInstanceOf(TupleType::class, $type); + } + + public function testCreateMultipleElementTuple(): void + { + $type = $this->factory->create('(U8, U32, U64)'); + $this->assertInstanceOf(TupleType::class, $type); + } + + public function testCreateNestedTuple(): void + { + $type = $this->factory->create('(U8, (U32, U64))'); + $this->assertInstanceOf(TupleType::class, $type); + } + + // ==================== Fixed Array Tests ==================== + + public function testCreateFixedArrayU8(): void + { + $type = $this->factory->create('[U8; 32]'); + $this->assertInstanceOf(FixedArrayType::class, $type); + } + + public function testCreateFixedArrayU32(): void + { + $type = $this->factory->create('[U32; 16]'); + $this->assertInstanceOf(FixedArrayType::class, $type); + } + + public function testCreateFixedArrayWithSpaces(): void + { + $type = $this->factory->create('[U8;32]'); + $this->assertInstanceOf(FixedArrayType::class, $type); + } + + // ==================== Invalid Type Tests ==================== + + public function testInvalidTypeThrowsException(): void + { + $this->expectException(InvalidTypeException::class); + $this->factory->create('NonExistentType'); + } + + public function testInvalidFixedArrayFormat(): void + { + $this->expectException(InvalidTypeException::class); + $this->factory->create('[U8]'); + } + + public function testZeroLengthFixedArrayThrows(): void + { + $this->expectException(InvalidTypeException::class); + $this->factory->create('[U8; 0]'); + } + + // ==================== Validation Tests ==================== + + public function testIsValidTypeStringForValidTypes(): void + { + $this->assertTrue($this->factory->isValidTypeString('U8')); + $this->assertTrue($this->factory->isValidTypeString('Vec')); + $this->assertTrue($this->factory->isValidTypeString('Option')); + $this->assertTrue($this->factory->isValidTypeString('(U8, U32)')); + $this->assertTrue($this->factory->isValidTypeString('[U8; 32]')); + } + + public function testIsValidTypeStringForInvalidTypes(): void + { + $this->assertFalse($this->factory->isValidTypeString('NonExistentType')); + } + + // ==================== Cache Tests ==================== + + public function testTypeCaching(): void + { + $type1 = $this->factory->create('Vec'); + $type2 = $this->factory->create('Vec'); + + // Should be different instances (clones from cache) + $this->assertNotSame($type1, $type2); + } + + public function testClearCache(): void + { + $this->factory->create('Vec'); + $this->factory->clearCache(); + // Should still work after clear + $type = $this->factory->create('Vec'); + $this->assertInstanceOf(VecType::class, $type); + } + + // ==================== Edge Cases ==================== + + public function testWhitespaceHandling(): void + { + $type = $this->factory->create(' Vec< U8 > '); + $this->assertInstanceOf(VecType::class, $type); + } + + public function testComplexNestedType(): void + { + $type = $this->factory->create('Option>'); + $this->assertInstanceOf(OptionType::class, $type); + } +}