Skip to content

Commit 850eb6f

Browse files
committed
test: add cast coverage and standardize on testify
- add table-driven tests for the new internal/cast helpers, covering happy paths plus overflow and parse errors for each converter - switch encoding and subtle tests to use require/assertions from testify so the suite uses one assertion style - keep go test ./... passing to validate the expanded coverage
1 parent f7d2c56 commit 850eb6f

8 files changed

Lines changed: 166 additions & 25 deletions

File tree

argon2/argon2id.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"golang.org/x/crypto/argon2"
99

10+
"github.com/allisson/go-pwdhash/internal/cast"
1011
"github.com/allisson/go-pwdhash/internal/encoding"
1112
"github.com/allisson/go-pwdhash/internal/subtle"
1213
)
@@ -74,17 +75,33 @@ func (a *Argon2idHasher) Verify(password []byte, encoded string) (bool, error) {
7475
return false, err
7576
}
7677

77-
mem, _ := strconv.Atoi(parsed.Params["m"])
78-
it, _ := strconv.Atoi(parsed.Params["t"])
79-
par, _ := strconv.Atoi(parsed.Params["p"])
78+
mem, err := cast.ConvertStringToUint32(parsed.Params["m"])
79+
if err != nil {
80+
return false, err
81+
}
82+
83+
it, err := cast.ConvertStringToUint32(parsed.Params["t"])
84+
if err != nil {
85+
return false, err
86+
}
87+
88+
par, err := cast.ConvertStringToUint8(parsed.Params["p"])
89+
if err != nil {
90+
return false, err
91+
}
92+
93+
keyLen, err := cast.ConvertIntToUint32(len(parsed.Hash))
94+
if err != nil {
95+
return false, err
96+
}
8097

8198
key := argon2.IDKey(
8299
password,
83100
parsed.Salt,
84-
uint32(it),
85-
uint32(mem),
86-
uint8(par),
87-
uint32(len(parsed.Hash)),
101+
it,
102+
mem,
103+
par,
104+
keyLen,
88105
)
89106

90107
return subtle.ConstantTimeCompare(key, parsed.Hash), nil

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module github.com/allisson/go-pwdhash
33
go 1.24.0
44

55
require (
6-
github.com/stretchr/testify v1.9.0
6+
github.com/ccoveille/go-safecast/v2 v2.0.0
7+
github.com/stretchr/testify v1.11.1
78
golang.org/x/crypto v0.47.0
89
)
910

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
2+
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
13
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
46
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
6-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
8+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
79
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
810
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
911
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=

internal/cast/cast.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cast
2+
3+
import (
4+
"strconv"
5+
6+
"github.com/ccoveille/go-safecast/v2"
7+
)
8+
9+
// ConvertStringToUint32 parses a base-10 string and returns a uint32 value.
10+
func ConvertStringToUint32(s string) (uint32, error) {
11+
i, err := strconv.ParseInt(s, 10, 32)
12+
if err != nil {
13+
return uint32(0), err
14+
}
15+
16+
return safecast.Convert[uint32](i)
17+
}
18+
19+
// ConvertStringToUint8 parses a base-10 string and returns a uint8 value.
20+
func ConvertStringToUint8(s string) (uint8, error) {
21+
i, err := strconv.ParseInt(s, 10, 32)
22+
if err != nil {
23+
return uint8(0), err
24+
}
25+
26+
return safecast.Convert[uint8](i)
27+
}
28+
29+
// ConvertIntToUint32 safely casts an int to uint32.
30+
func ConvertIntToUint32(i int) (uint32, error) {
31+
return safecast.Convert[uint32](i)
32+
}

internal/cast/cast_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cast
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestConvertStringToUint32(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
want uint32
14+
wantErr bool
15+
}{
16+
{name: "ok", input: "12345", want: 12345},
17+
{name: "nonNumeric", input: "abc", wantErr: true},
18+
{name: "negative", input: "-1", wantErr: true},
19+
}
20+
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
got, err := ConvertStringToUint32(tt.input)
24+
if tt.wantErr {
25+
require.Error(t, err)
26+
return
27+
}
28+
29+
require.NoError(t, err)
30+
require.Equal(t, tt.want, got)
31+
})
32+
}
33+
}
34+
35+
func TestConvertStringToUint8(t *testing.T) {
36+
tests := []struct {
37+
name string
38+
input string
39+
want uint8
40+
wantErr bool
41+
}{
42+
{name: "ok", input: "255", want: 255},
43+
{name: "nonNumeric", input: "hello", wantErr: true},
44+
{name: "overflow", input: "512", wantErr: true},
45+
}
46+
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
got, err := ConvertStringToUint8(tt.input)
50+
if tt.wantErr {
51+
require.Error(t, err)
52+
return
53+
}
54+
55+
require.NoError(t, err)
56+
require.Equal(t, tt.want, got)
57+
})
58+
}
59+
}
60+
61+
func TestConvertIntToUint32(t *testing.T) {
62+
tests := []struct {
63+
name string
64+
input int
65+
want uint32
66+
wantErr bool
67+
}{
68+
{name: "zero", input: 0, want: 0},
69+
{name: "positive", input: 65535, want: 65535},
70+
{name: "negative", input: -1, wantErr: true},
71+
}
72+
73+
for _, tt := range tests {
74+
t.Run(tt.name, func(t *testing.T) {
75+
got, err := ConvertIntToUint32(tt.input)
76+
if tt.wantErr {
77+
require.Error(t, err)
78+
return
79+
}
80+
81+
require.NoError(t, err)
82+
require.Equal(t, tt.want, got)
83+
})
84+
}
85+
}

internal/encoding/encoding_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package encoding
33
import (
44
"testing"
55

6-
"github.com/stretchr/testify/assert"
76
"github.com/stretchr/testify/require"
87
)
98

@@ -26,10 +25,10 @@ func TestParse_ValidPHCString(t *testing.T) {
2625
require.NoError(t, err)
2726
require.NotNil(t, parsed)
2827

29-
assert.Equal(t, original.Algorithm, parsed.Algorithm)
30-
assert.Equal(t, original.Params, parsed.Params)
31-
assert.Equal(t, original.Salt, parsed.Salt)
32-
assert.Equal(t, original.Hash, parsed.Hash)
28+
require.Equal(t, original.Algorithm, parsed.Algorithm)
29+
require.Equal(t, original.Params, parsed.Params)
30+
require.Equal(t, original.Salt, parsed.Salt)
31+
require.Equal(t, original.Hash, parsed.Hash)
3332
}
3433

3534
func TestParse_InvalidPHCString(t *testing.T) {
@@ -50,7 +49,7 @@ func TestEncodedHashStringIncludesMetadata(t *testing.T) {
5049

5150
encoded := enc.String()
5251

53-
assert.Contains(t, encoded, "$argon2id$")
54-
assert.Contains(t, encoded, "$v=19$")
55-
assert.Contains(t, encoded, "m=65536")
52+
require.Contains(t, encoded, "$argon2id$")
53+
require.Contains(t, encoded, "$v=19$")
54+
require.Contains(t, encoded, "m=65536")
5655
}

internal/encoding/parse.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import (
77
)
88

99
// Parse decodes a PHC-formatted string into an EncodedHash structure.
10+
//
11+
// The input must follow the "$<id>$v=<ver>$<params>$<salt>$<hash>" convention
12+
// defined by the PHC string format. Parameters are stored verbatim without
13+
// validation to keep parsing focused on syntax; callers must enforce any
14+
// semantic constraints.
1015
func Parse(s string) (*EncodedHash, error) {
1116
parts := strings.Split(s, "$")
1217
if len(parts) < 6 {

internal/subtle/compare_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package subtle
22

3-
import "testing"
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
48

59
func TestConstantTimeCompare(t *testing.T) {
610
a := []byte("same-length")
711
b := []byte("same-length")
812

9-
if !ConstantTimeCompare(a, b) {
10-
t.Fatalf("expected slices to be equal")
11-
}
13+
require.True(t, ConstantTimeCompare(a, b))
1214

1315
different := []byte("different")
14-
if ConstantTimeCompare(a, different) {
15-
t.Fatalf("expected mismatch to return false")
16-
}
16+
require.False(t, ConstantTimeCompare(a, different))
1717
}

0 commit comments

Comments
 (0)