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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -118,12 +119,15 @@ func Fetch(c context.T, account string) (resp *WellKnownResponse,
|
|||||||
}
|
}
|
||||||
defer func() { _ = res.Body.Close() }()
|
defer func() { _ = res.Body.Close() }()
|
||||||
resp = NewWellKnownResponse()
|
resp = NewWellKnownResponse()
|
||||||
b := make([]byte, 65535)
|
|
||||||
var n int
|
// Read the entire response body
|
||||||
if n, err = res.Body.Read(b); chk.E(err) {
|
var b []byte
|
||||||
|
if b, err = io.ReadAll(res.Body); chk.E(err) {
|
||||||
|
err = errorf.E("failed to read response body: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b = b[:n]
|
|
||||||
|
// Unmarshal the JSON response
|
||||||
if err = json.Unmarshal(b, resp); chk.E(err) {
|
if err = json.Unmarshal(b, resp); chk.E(err) {
|
||||||
err = errorf.E("failed to decode json response: %w", 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
|
// IsValid32ByteHex checks that a hex string is a valid 32 bytes lower case hex encoded value as
|
||||||
// per nostr NIP-01 spec.
|
// per nostr NIP-01 spec.
|
||||||
func IsValid32ByteHex[V []byte | string](pk V) bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
dec := make([]byte, 32)
|
// Check if the input length is exactly 64 characters (32 bytes * 2)
|
||||||
if _, err = hex.DecBytes(dec, []byte(pk)); chk.E(err) {
|
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.
|
// IsValidPublicKey checks that a hex encoded public key is a valid BIP-340 public key.
|
||||||
func IsValidPublicKey[V []byte | string](pk V) bool {
|
func IsValidPublicKey[V []byte | string](pk V) bool {
|
||||||
v, _ := hex.Dec(string(pk))
|
v, err := hex.Dec(string(pk))
|
||||||
_, err := schnorr.ParsePubKey(v)
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = schnorr.ParsePubKey(v)
|
||||||
return err == nil
|
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