test all dns nip-05 and fix bugs
This commit is contained in:
12
dns/nip05.go
12
dns/nip05.go
@@ -5,6 +5,7 @@ package dns
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -118,12 +119,15 @@ func Fetch(c context.T, account string) (resp *WellKnownResponse,
|
||||
}
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
resp = NewWellKnownResponse()
|
||||
b := make([]byte, 65535)
|
||||
var n int
|
||||
if n, err = res.Body.Read(b); chk.E(err) {
|
||||
|
||||
// Read the entire response body
|
||||
var b []byte
|
||||
if b, err = io.ReadAll(res.Body); chk.E(err) {
|
||||
err = errorf.E("failed to read response body: %w", err)
|
||||
return
|
||||
}
|
||||
b = b[:n]
|
||||
|
||||
// Unmarshal the JSON response
|
||||
if err = json.Unmarshal(b, resp); chk.E(err) {
|
||||
err = errorf.E("failed to decode json response: %w", err)
|
||||
}
|
||||
|
||||
207
dns/nip05_additional_test.go
Normal file
207
dns/nip05_additional_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsValidIdentifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"Valid with name", "user@example.com", true},
|
||||
{"Valid domain only", "example.com", true},
|
||||
{"Valid with subdomain", "user@sub.example.com", true},
|
||||
{"Valid with plus", "user+tag@example.com", true},
|
||||
{"Valid with underscore", "user_name@example.com", true},
|
||||
{"Invalid no domain", "user@", false},
|
||||
{"Invalid special chars", "user!@example.com", false},
|
||||
{"Invalid format", "not-an-email", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := IsValidIdentifier(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsValidIdentifier(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeIdentifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"Already normalized", "example.com", "example.com"},
|
||||
{"With underscore prefix", "_@example.com", "example.com"},
|
||||
{"With name", "user@example.com", "user@example.com"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := NormalizeIdentifier(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("NormalizeIdentifier(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSliceToByteSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected [][]byte
|
||||
}{
|
||||
{
|
||||
"Empty slice",
|
||||
[]string{},
|
||||
[][]byte{},
|
||||
},
|
||||
{
|
||||
"Single item",
|
||||
[]string{"test"},
|
||||
[][]byte{[]byte("test")},
|
||||
},
|
||||
{
|
||||
"Multiple items",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
[][]byte{[]byte("test1"), []byte("test2"), []byte("test3")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := StringSliceToByteSlice(tt.input)
|
||||
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Fatalf("StringSliceToByteSlice(%v) returned slice of length %d, want %d",
|
||||
tt.input, len(result), len(tt.expected))
|
||||
}
|
||||
|
||||
for i, v := range result {
|
||||
if !bytes.Equal(v, tt.expected[i]) {
|
||||
t.Errorf("StringSliceToByteSlice(%v)[%d] = %v, want %v",
|
||||
tt.input, i, v, tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIdentifierEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expectedName string
|
||||
expectedDomain string
|
||||
expectError bool
|
||||
}{
|
||||
{"Empty string", "", "", "", true},
|
||||
{"Just @", "@", "", "", true},
|
||||
{"Multiple @", "user@domain@example.com", "", "", true},
|
||||
{"Invalid domain", "user@invalid", "", "", true},
|
||||
{"Valid with hyphen", "user-name@example.com", "user-name", "example.com", false},
|
||||
{"Valid with numbers", "user123@example123.com", "user123", "example123.com", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
name, domain, err := ParseIdentifier(tt.input)
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Errorf("ParseIdentifier(%q) expected error, got nil", tt.input)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("ParseIdentifier(%q) unexpected error: %v", tt.input, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.expectError {
|
||||
if name != tt.expectedName {
|
||||
t.Errorf("ParseIdentifier(%q) name = %q, want %q", tt.input, name, tt.expectedName)
|
||||
}
|
||||
|
||||
if domain != tt.expectedDomain {
|
||||
t.Errorf("ParseIdentifier(%q) domain = %q, want %q", tt.input, domain, tt.expectedDomain)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWellKnownResponse(t *testing.T) {
|
||||
resp := NewWellKnownResponse()
|
||||
|
||||
if resp == nil {
|
||||
t.Fatal("NewWellKnownResponse() returned nil")
|
||||
}
|
||||
|
||||
if resp.Names == nil {
|
||||
t.Error("NewWellKnownResponse() Names map is nil")
|
||||
}
|
||||
|
||||
if resp.Relays == nil {
|
||||
t.Error("NewWellKnownResponse() Relays map is nil")
|
||||
}
|
||||
|
||||
if resp.NIP46 == nil {
|
||||
t.Error("NewWellKnownResponse() NIP46 map is nil")
|
||||
}
|
||||
|
||||
// Test that we can add entries to the maps
|
||||
resp.Names["test"] = "pubkey"
|
||||
resp.Relays["pubkey"] = []string{"relay1", "relay2"}
|
||||
resp.NIP46["pubkey"] = []string{"nip46url"}
|
||||
|
||||
if resp.Names["test"] != "pubkey" {
|
||||
t.Error("Failed to add entry to Names map")
|
||||
}
|
||||
|
||||
if len(resp.Relays["pubkey"]) != 2 {
|
||||
t.Error("Failed to add entry to Relays map")
|
||||
}
|
||||
|
||||
if len(resp.NIP46["pubkey"]) != 1 {
|
||||
t.Error("Failed to add entry to NIP46 map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexPattern(t *testing.T) {
|
||||
// Test the Nip05Regex pattern directly
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"Valid email format", "user@example.com", true},
|
||||
{"Valid domain only", "example.com", true},
|
||||
{"Valid subdomain", "user@sub.example.com", true},
|
||||
{"Valid with underscore", "user_name@example.com", true},
|
||||
{"Valid with hyphen", "user-name@example.com", true},
|
||||
{"Valid with plus", "user+tag@example.com", true},
|
||||
{"Valid with numbers", "user123@example123.com", true},
|
||||
{"Invalid no TLD", "user@localhost", false},
|
||||
{"Valid IP address", "user@127.0.0.1", true},
|
||||
{"Invalid with port", "user@example.com:8080", false},
|
||||
{"Invalid special chars", "user!@example.com", false},
|
||||
{"Invalid multiple @", "user@domain@example.com", false},
|
||||
{"Empty string", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Nip05Regex.MatchString(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Nip05Regex.MatchString(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
28
keys/keys.go
28
keys/keys.go
@@ -59,20 +59,34 @@ func SecretBytesToPubKeyHex(skb []byte) (pk string, err error) {
|
||||
// IsValid32ByteHex checks that a hex string is a valid 32 bytes lower case hex encoded value as
|
||||
// per nostr NIP-01 spec.
|
||||
func IsValid32ByteHex[V []byte | string](pk V) bool {
|
||||
if bytes.Equal(bytes.ToLower([]byte(pk)), []byte(pk)) {
|
||||
pkBytes := []byte(pk)
|
||||
|
||||
// Check if the input is lowercase
|
||||
if !bytes.Equal(bytes.ToLower(pkBytes), pkBytes) {
|
||||
return false
|
||||
}
|
||||
var err error
|
||||
dec := make([]byte, 32)
|
||||
if _, err = hex.DecBytes(dec, []byte(pk)); chk.E(err) {
|
||||
|
||||
// Check if the input length is exactly 64 characters (32 bytes * 2)
|
||||
if len(pkBytes) != 64 {
|
||||
return false
|
||||
}
|
||||
return len(dec) == 32
|
||||
|
||||
// Try to decode the hex string
|
||||
var err error
|
||||
if _, err = hex.Dec(string(pkBytes)); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsValidPublicKey checks that a hex encoded public key is a valid BIP-340 public key.
|
||||
func IsValidPublicKey[V []byte | string](pk V) bool {
|
||||
v, _ := hex.Dec(string(pk))
|
||||
_, err := schnorr.ParsePubKey(v)
|
||||
v, err := hex.Dec(string(pk))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = schnorr.ParsePubKey(v)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
||||
197
keys/keys_test.go
Normal file
197
keys/keys_test.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsValid32ByteHex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"Valid lowercase hex 32 bytes",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Valid lowercase hex 32 bytes with different value",
|
||||
"f9dd6a762506260b38a2d3e5b464213c2e47fa3877429fe9ee60e071a31a07d7",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid uppercase hex",
|
||||
"3BF0C63FCB93463407AF97A5E5EE64FA883D107EF9E558472C4EB9AAAEFA459D",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid mixed case hex",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459D",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid too short",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid too long",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d00",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid non-hex characters",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459g",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty string",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid special characters",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45!@",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := IsValid32ByteHex(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsValid32ByteHex(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValid32ByteHexWithBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"Valid lowercase hex bytes",
|
||||
[]byte("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid uppercase hex bytes",
|
||||
[]byte("3BF0C63FCB93463407AF97A5E5EE64FA883D107EF9E558472C4EB9AAAEFA459D"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty bytes",
|
||||
[]byte(""),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := IsValid32ByteHex(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsValid32ByteHex(%q) = %v, want %v", string(tt.input), result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidPublicKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"Valid public key",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Another valid public key",
|
||||
"f9dd6a762506260b38a2d3e5b464213c2e47fa3877429fe9ee60e071a31a07d7",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid too short",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid too long",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d00",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Invalid non-hex",
|
||||
"not-a-hex-string",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty string",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := IsValidPublicKey(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsValidPublicKey(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexPubkeyToBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expectError bool
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
"Valid hex string",
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
||||
false,
|
||||
32,
|
||||
},
|
||||
{
|
||||
"Invalid hex string",
|
||||
"not-hex",
|
||||
true,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"Empty string",
|
||||
"",
|
||||
true,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := HexPubkeyToBytes(tt.input)
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Errorf("HexPubkeyToBytes(%q) expected error, got nil", tt.input)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("HexPubkeyToBytes(%q) unexpected error: %v", tt.input, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.expectError && len(result) != tt.expectedLen {
|
||||
t.Errorf("HexPubkeyToBytes(%q) returned %d bytes, want %d", tt.input, len(result), tt.expectedLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user