Skip to content

Commit fbf2200

Browse files
test(coverage): drive storageprovider/{dospaces,s3} to >=95% (#31)
Caps the cross-repo 95% coverage push for `common`. After PRs #25-#30 landed for crypto/nats/plans/readiness/r2/logctx, the two remaining storage backends sat at 63.6% (dospaces) and 72.2% (s3). dospaces -> 100.0% via accessor + customerEndpointURL fallback coverage: all simple getters (MasterAccessKey, MasterSecretKey, Endpoint, PublicURL, Bucket, Region, UseTLS) plus the scheme-prefix and already-schemed endpoint branches inside customerEndpointURL(). s3 -> 97.2% via accessor + IssueTenantCredentials default-fallback + safeSessionName branch coverage: digits/dashes/underscores retained, RoleSessionName truncation at 56, customerEndpointURL passthrough for already-schemed endpoints, RevokeTenantCredentials no-op confirmation, and AssumeRole error propagation through IssueTenantCredentials. Total: 94.5% -> 98.0%. Every package now >=95%. Coverage block (rule 17): Symptom: common total 94.5% < 95% target; per-pkg dospaces/s3 below floor Enumeration: go tool cover -func | sort | grep "<100" Sites found: 2 (storageprovider/dospaces, storageprovider/s3) Sites touched: both Coverage test: TestRegistry_AllProvidersSatisfyContract + per-backend registry-walk tests already in place; new coverage files add accessor + edge-branch assertions. Live verified: go test ./... -short -coverprofile cov.out; total = 98.0% Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4473edd commit fbf2200

2 files changed

Lines changed: 306 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package dospaces_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"instant.dev/common/storageprovider"
10+
"instant.dev/common/storageprovider/dospaces"
11+
)
12+
13+
// TestGetters_AndCustomerEndpointFallbacks exercises every simple accessor on
14+
// the Provider plus the fallback paths inside customerEndpointURL() that the
15+
// happy-path tests don't reach.
16+
//
17+
// Why each branch matters:
18+
//
19+
// - MasterAccessKey / MasterSecretKey / Endpoint / Bucket / Region / UseTLS
20+
// are read by the api at boot when building the presigner for broker mode;
21+
// if any returns "" we ship an unsigned URL into a customer's logs.
22+
// - PublicURL() with publicURL=="" must fall through customerEndpointURL()
23+
// so we never return an empty string (broker-mode would silently 404).
24+
// - customerEndpointURL() with UseTLS=false should prepend "http://", and
25+
// when the endpoint already carries a scheme (some operators wire
26+
// `https://...`) it should pass through unmodified.
27+
func TestGetters_AndCustomerEndpointFallbacks(t *testing.T) {
28+
t.Run("all_getters_return_configured_values", func(t *testing.T) {
29+
p, err := dospaces.New(storageprovider.Config{
30+
Endpoint: "nyc3.digitaloceanspaces.com",
31+
PublicURL: "https://s3.instanode.dev",
32+
Region: "nyc3",
33+
Bucket: "instant-shared",
34+
MasterKey: "MK",
35+
MasterSecret: "MS",
36+
UseTLS: true,
37+
})
38+
require.NoError(t, err)
39+
prov := p.(*dospaces.Provider)
40+
41+
assert.Equal(t, "MK", prov.MasterAccessKey())
42+
assert.Equal(t, "MS", prov.MasterSecretKey())
43+
assert.Equal(t, "nyc3.digitaloceanspaces.com", prov.Endpoint())
44+
assert.Equal(t, "https://s3.instanode.dev", prov.PublicURL())
45+
assert.Equal(t, "instant-shared", prov.Bucket())
46+
assert.Equal(t, "nyc3", prov.Region())
47+
assert.True(t, prov.UseTLS())
48+
})
49+
50+
t.Run("public_url_falls_back_to_scheme_prefixed_endpoint_no_tls", func(t *testing.T) {
51+
p, err := dospaces.New(storageprovider.Config{
52+
Endpoint: "minio.local:9000",
53+
MasterKey: "MK",
54+
MasterSecret: "MS",
55+
UseTLS: false,
56+
})
57+
require.NoError(t, err)
58+
prov := p.(*dospaces.Provider)
59+
60+
// PublicURL must fall back to customerEndpointURL ("http://minio.local:9000").
61+
assert.Equal(t, "http://minio.local:9000", prov.PublicURL())
62+
assert.False(t, prov.UseTLS())
63+
})
64+
65+
t.Run("public_url_falls_back_to_scheme_prefixed_endpoint_tls", func(t *testing.T) {
66+
p, err := dospaces.New(storageprovider.Config{
67+
Endpoint: "nyc3.digitaloceanspaces.com",
68+
MasterKey: "MK",
69+
MasterSecret: "MS",
70+
UseTLS: true,
71+
})
72+
require.NoError(t, err)
73+
prov := p.(*dospaces.Provider)
74+
assert.Equal(t, "https://nyc3.digitaloceanspaces.com", prov.PublicURL())
75+
})
76+
77+
t.Run("endpoint_with_existing_scheme_passes_through", func(t *testing.T) {
78+
// Some operator wires OBJECT_STORE_ENDPOINT=https://foo.bar — the
79+
// customerEndpointURL must NOT double-prefix.
80+
p, err := dospaces.New(storageprovider.Config{
81+
Endpoint: "https://already-schemed.example",
82+
MasterKey: "MK",
83+
MasterSecret: "MS",
84+
UseTLS: false,
85+
})
86+
require.NoError(t, err)
87+
prov := p.(*dospaces.Provider)
88+
assert.Equal(t, "https://already-schemed.example", prov.PublicURL())
89+
})
90+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package s3_test
2+
3+
import (
4+
"context"
5+
"strings"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"instant.dev/common/storageprovider"
13+
"instant.dev/common/storageprovider/s3"
14+
)
15+
16+
// TestS3_New_RequiresMasterCreds — sts:AssumeRole requires platform creds.
17+
func TestS3_New_RequiresMasterCreds(t *testing.T) {
18+
_, err := s3.New(storageprovider.Config{
19+
AWSRoleARN: "arn:aws:iam::123:role/x",
20+
})
21+
require.Error(t, err)
22+
assert.Contains(t, err.Error(), "OBJECT_STORE_ACCESS_KEY")
23+
}
24+
25+
// TestS3_Getters_AndCustomerEndpointFallbacks covers Master*/Endpoint/Bucket/
26+
// Region/PublicURL/Name + the two customerEndpointURL branches (already-schemed
27+
// and bare-host).
28+
func TestS3_Getters_AndCustomerEndpointFallbacks(t *testing.T) {
29+
t.Run("bare_host_gets_https_prefix", func(t *testing.T) {
30+
raw, err := s3.New(storageprovider.Config{
31+
AWSRoleARN: "arn:aws:iam::123:role/x",
32+
MasterKey: "MK",
33+
MasterSecret: "MS",
34+
})
35+
require.NoError(t, err)
36+
p := raw.(*s3.Provider)
37+
38+
assert.Equal(t, "MK", p.MasterAccessKey())
39+
assert.Equal(t, "MS", p.MasterSecretKey())
40+
assert.Equal(t, "s3.us-east-1.amazonaws.com", p.Endpoint())
41+
assert.Equal(t, "instant-shared", p.Bucket())
42+
assert.Equal(t, "us-east-1", p.Region())
43+
assert.Equal(t, "s3", p.Name())
44+
assert.Equal(t, "https://s3.us-east-1.amazonaws.com", p.PublicURL())
45+
})
46+
47+
t.Run("endpoint_already_schemed_passes_through", func(t *testing.T) {
48+
raw, err := s3.New(storageprovider.Config{
49+
Endpoint: "https://custom.example",
50+
AWSRoleARN: "arn:aws:iam::123:role/x",
51+
MasterKey: "MK",
52+
MasterSecret: "MS",
53+
})
54+
require.NoError(t, err)
55+
p := raw.(*s3.Provider)
56+
assert.Equal(t, "https://custom.example", p.PublicURL())
57+
})
58+
59+
t.Run("public_url_set_overrides_endpoint", func(t *testing.T) {
60+
raw, err := s3.New(storageprovider.Config{
61+
Endpoint: "s3.amazonaws.com",
62+
PublicURL: "https://cdn.example.dev",
63+
AWSRoleARN: "arn:aws:iam::123:role/x",
64+
MasterKey: "MK",
65+
MasterSecret: "MS",
66+
})
67+
require.NoError(t, err)
68+
p := raw.(*s3.Provider)
69+
assert.Equal(t, "https://cdn.example.dev", p.PublicURL())
70+
})
71+
72+
t.Run("region_defaults_and_bucket_defaults_apply", func(t *testing.T) {
73+
raw, err := s3.New(storageprovider.Config{
74+
AWSRoleARN: "arn:aws:iam::123:role/x",
75+
MasterKey: "MK",
76+
MasterSecret: "MS",
77+
})
78+
require.NoError(t, err)
79+
p := raw.(*s3.Provider)
80+
assert.Equal(t, "us-east-1", p.Region())
81+
assert.Equal(t, "instant-shared", p.Bucket())
82+
})
83+
}
84+
85+
// TestS3_RevokeTenantCredentials_IsNoOp — STS sessions can't be revoked, the
86+
// method must return nil regardless of input.
87+
func TestS3_RevokeTenantCredentials_IsNoOp(t *testing.T) {
88+
raw, err := s3.New(storageprovider.Config{
89+
AWSRoleARN: "arn:aws:iam::123:role/x",
90+
MasterKey: "MK",
91+
MasterSecret: "MS",
92+
})
93+
require.NoError(t, err)
94+
p := raw.(*s3.Provider)
95+
96+
require.NoError(t, p.RevokeTenantCredentials(context.Background(), ""))
97+
require.NoError(t, p.RevokeTenantCredentials(context.Background(), "any-key"))
98+
}
99+
100+
// TestS3_IssueTenantCredentials_DefaultsTTLAndPrefix covers:
101+
// - TTL==0 falls through to the 1-hour default
102+
// - Empty Prefix falls back to ResourceToken
103+
// - Empty Bucket falls back to provider default
104+
func TestS3_IssueTenantCredentials_DefaultsTTLAndPrefix(t *testing.T) {
105+
raw, err := s3.New(storageprovider.Config{
106+
Region: "us-west-2",
107+
Bucket: "my-default",
108+
AWSRoleARN: "arn:aws:iam::123:role/x",
109+
MasterKey: "MK",
110+
MasterSecret: "MS",
111+
})
112+
require.NoError(t, err)
113+
p := raw.(*s3.Provider)
114+
115+
var captured s3.AssumeRoleInput
116+
p.SetAssumeRoleFunc(func(ctx context.Context, in s3.AssumeRoleInput) (*s3.AssumeRoleOutput, error) {
117+
captured = in
118+
return &s3.AssumeRoleOutput{
119+
AccessKeyID: "AK",
120+
SecretAccessKey: "SK",
121+
SessionToken: "TOK",
122+
Expiration: time.Now().Add(time.Hour),
123+
}, nil
124+
})
125+
126+
creds, err := p.IssueTenantCredentials(context.Background(), storageprovider.IssueRequest{
127+
ResourceToken: "tok-ABC",
128+
// No Prefix, no Bucket, no TTL — exercises every fallback line.
129+
})
130+
require.NoError(t, err)
131+
assert.Equal(t, "tok-ABC", creds.Prefix, "prefix must default to resource token")
132+
assert.Equal(t, "my-default", creds.Bucket, "bucket must default to provider default")
133+
assert.Equal(t, int32(3600), captured.DurationSeconds, "TTL==0 must default to 1h")
134+
}
135+
136+
// TestS3_SafeSessionName covers the trimming + min-length fallback. RoleSessionName
137+
// is constrained by AWS to [\w+=,.@-] and length 2..64; safeSessionName must drop
138+
// disallowed characters AND backfill "instanode-x" when the result is too short
139+
// to be a valid session name.
140+
func TestS3_SafeSessionName(t *testing.T) {
141+
raw, err := s3.New(storageprovider.Config{
142+
AWSRoleARN: "arn:aws:iam::123:role/x",
143+
MasterKey: "MK",
144+
MasterSecret: "MS",
145+
})
146+
require.NoError(t, err)
147+
p := raw.(*s3.Provider)
148+
149+
cases := []struct {
150+
name string
151+
token string
152+
expected string
153+
}{
154+
{
155+
name: "all_disallowed_chars_falls_back_to_instanode_x",
156+
token: "!!!@@@###",
157+
expected: "instanode-instanode-x",
158+
},
159+
{
160+
name: "long_token_truncated_to_56",
161+
token: strings.Repeat("a", 200),
162+
expected: "instanode-" + strings.Repeat("a", 56),
163+
},
164+
{
165+
name: "digits_underscores_dashes_kept",
166+
token: "tenant-01_abc",
167+
expected: "instanode-tenant-01_abc",
168+
},
169+
}
170+
for _, c := range cases {
171+
t.Run(c.name, func(t *testing.T) {
172+
var captured s3.AssumeRoleInput
173+
p.SetAssumeRoleFunc(func(ctx context.Context, in s3.AssumeRoleInput) (*s3.AssumeRoleOutput, error) {
174+
captured = in
175+
return &s3.AssumeRoleOutput{
176+
AccessKeyID: "AK",
177+
SecretAccessKey: "SK",
178+
SessionToken: "TOK",
179+
Expiration: time.Now().Add(15 * time.Minute),
180+
}, nil
181+
})
182+
_, err := p.IssueTenantCredentials(context.Background(), storageprovider.IssueRequest{
183+
ResourceToken: c.token,
184+
TTL: 15 * time.Minute,
185+
})
186+
require.NoError(t, err)
187+
assert.Equal(t, c.expected, captured.RoleSessionName)
188+
})
189+
}
190+
}
191+
192+
// TestS3_AssumeRoleError_Propagates verifies the error from a failing
193+
// AssumeRole call surfaces to the caller rather than being swallowed into
194+
// a "best-effort" silent return.
195+
func TestS3_AssumeRoleError_Propagates(t *testing.T) {
196+
raw, err := s3.New(storageprovider.Config{
197+
AWSRoleARN: "arn:aws:iam::123:role/x",
198+
MasterKey: "MK",
199+
MasterSecret: "MS",
200+
})
201+
require.NoError(t, err)
202+
p := raw.(*s3.Provider)
203+
p.SetAssumeRoleFunc(func(ctx context.Context, in s3.AssumeRoleInput) (*s3.AssumeRoleOutput, error) {
204+
return nil, assertError("aws-sdk: throttled")
205+
})
206+
_, err = p.IssueTenantCredentials(context.Background(), storageprovider.IssueRequest{
207+
ResourceToken: "x",
208+
TTL: 15 * time.Minute,
209+
})
210+
require.Error(t, err)
211+
assert.Contains(t, err.Error(), "throttled")
212+
}
213+
214+
type assertError string
215+
216+
func (a assertError) Error() string { return string(a) }

0 commit comments

Comments
 (0)