Repository navigation
Expand file tree
/
Copy pathpointers_test.go
More file actions
165 lines (149 loc) · 3.92 KB
/
Copy pathpointers_test.go
File metadata and controls
165 lines (149 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package env_test
import (
"errors"
"testing"
"github.com/goloop/env/v2"
)
type ptrInner struct {
X int `env:"X"`
}
// TestNoPanicPublicAPI is the safety net: no public call may panic on input a
// user can realistically pass (nil objects, nil pointer fields, nil slice
// elements, unexported fields).
func TestNoPanicPublicAPI(t *testing.T) {
cases := []struct {
name string
fn func()
}{
{"MarshalMap(nil)", func() { env.MarshalMap(nil) }},
{"Marshal(nil)", func() { env.Marshal(nil) }},
{"Unmarshal(nil)", func() { env.Unmarshal(nil) }},
{"MarshalMap nil scalar ptr field", func() {
env.MarshalMap(struct {
P *int `env:"P"`
}{})
}},
{"MarshalMap nil struct ptr field", func() {
env.MarshalMap(struct {
I *ptrInner `env:"I"`
}{})
}},
{"MarshalMap nil slice element", func() {
env.MarshalMap(struct {
A []*int `env:"A" sep:","`
}{A: []*int{nil}})
}},
{"Unmarshal nil scalar ptr field", func() {
var s struct {
P *int `env:"P"`
}
env.UnmarshalMap(map[string]string{"P": "5"}, &s)
}},
{"Unmarshal unexported field", func() {
var s struct {
Port int `env:"PORT"`
secret string
}
_ = s.secret
env.UnmarshalMap(map[string]string{"PORT": "9090"}, &s)
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("panicked: %v", r)
}
}()
c.fn()
})
}
}
// TestPointerContract verifies the nil-pointer contract: allocate only when
// there is a value to assign; otherwise leave nil; marshal omits nil so the
// value round-trips.
func TestPointerContract(t *testing.T) {
type cfg struct {
P *int `env:"P"`
}
// Present -> allocate and set.
var present cfg
if err := env.UnmarshalMap(map[string]string{"P": "5"}, &present); err != nil {
t.Fatal(err)
}
if present.P == nil || *present.P != 5 {
t.Errorf("present: expected *5, got %v", present.P)
}
// Absent -> stay nil.
var absent cfg
if err := env.UnmarshalMap(map[string]string{}, &absent); err != nil {
t.Fatal(err)
}
if absent.P != nil {
t.Errorf("absent: expected nil, got %v", *absent.P)
}
// Marshal omits nil; round-trip keeps it nil.
m, err := env.MarshalMap(cfg{})
if err != nil {
t.Fatal(err)
}
if len(m) != 0 {
t.Errorf("nil pointer must be omitted, got %v", m)
}
var back cfg
env.UnmarshalMap(m, &back)
if back.P != nil {
t.Error("round-trip must keep nil")
}
}
// TestNestedStructPointer checks that an optional nested-struct pointer is
// allocated only when the source has keys under its prefix.
func TestNestedStructPointer(t *testing.T) {
type cfg struct {
I *ptrInner `env:"I"`
}
var withSub cfg
if err := env.UnmarshalMap(map[string]string{"I_X": "7"}, &withSub); err != nil {
t.Fatal(err)
}
if withSub.I == nil || withSub.I.X != 7 {
t.Errorf("expected I.X=7, got %v", withSub.I)
}
var noSub cfg
env.UnmarshalMap(map[string]string{}, &noSub)
if noSub.I != nil {
t.Error("nested pointer must stay nil when no subkeys are present")
}
}
// TestUnexportedFieldSkipped checks that unexported fields are ignored on both
// decode and encode (no panic), like encoding/json.
func TestUnexportedFieldSkipped(t *testing.T) {
type cfg struct {
Port int `env:"PORT"`
secret string
}
var c cfg
if err := env.UnmarshalMap(map[string]string{"PORT": "9090"}, &c); err != nil {
t.Fatal(err)
}
if c.Port != 9090 {
t.Errorf("Port=%d, want 9090", c.Port)
}
c.secret = "x"
m, err := env.MarshalMap(c)
if err != nil {
t.Fatal(err)
}
if _, ok := m["secret"]; ok {
t.Errorf("unexported field must not be marshaled: %v", m)
}
}
// TestMarshalNilObject checks that marshaling nil returns ErrNilObject.
func TestMarshalNilObject(t *testing.T) {
if _, err := env.MarshalMap(nil); !errors.Is(err, env.ErrNilObject) {
t.Errorf("expected ErrNilObject, got %v", err)
}
if err := env.Marshal(nil); !errors.Is(err, env.ErrNilObject) {
t.Errorf("expected ErrNilObject, got %v", err)
}
}