-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #73 from chialab/unit-test
Unit tests… sort of
- Loading branch information
Showing
8 changed files
with
255 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,9 @@ on: | |
push: | ||
tags: [ v* ] | ||
|
||
permissions: | ||
contents: write | ||
|
||
jobs: | ||
build: | ||
name: Build and release | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ on: | |
permissions: | ||
contents: write | ||
|
||
|
||
jobs: | ||
go-action-detection: | ||
name: Submit dependencies | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package ecr | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"errors" | ||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr/types" | ||
"strings" | ||
) | ||
|
||
// InvalidTokenError is returned when token is not in base64-encoded `username:password` format. | ||
var InvalidTokenError = errors.New("unexpected token format") | ||
|
||
// UnexpectedResponseError is returned when ECR client returns zero or more than one types.AuthorizationData in its response. | ||
var UnexpectedResponseError = errors.New("unexpected number of authorization data returned") | ||
|
||
// Interface required to obtain an ECR authentication token. | ||
type ecrTokenClient interface { | ||
GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) | ||
} | ||
|
||
// getClient returns an ecrTokenClient instantiating a new AWS ECR client using AWS default configuration with options. | ||
func getClient(context context.Context, optsFns ...func(*config.LoadOptions) error) (ecrTokenClient, error) { | ||
if cfg, err := config.LoadDefaultConfig(context, optsFns...); err != nil { | ||
return nil, err | ||
} else { | ||
return ecr.NewFromConfig(cfg), nil | ||
} | ||
} | ||
|
||
// Obtain a types.AuthorizationData from an ecrTokenClient. | ||
func getAuthData(context context.Context, client ecrTokenClient) (*types.AuthorizationData, error) { | ||
if token, err := client.GetAuthorizationToken(context, &ecr.GetAuthorizationTokenInput{}); err != nil { | ||
return nil, err | ||
} else if len(token.AuthorizationData) != 1 { | ||
return nil, UnexpectedResponseError | ||
} else { | ||
return &token.AuthorizationData[0], nil | ||
} | ||
} | ||
|
||
// AuthorizationToken extends types.AuthorizationData by adding Username and Password explicitly. | ||
type AuthorizationToken struct { | ||
types.AuthorizationData | ||
Username *string | ||
Password *string | ||
} | ||
|
||
// NewToken builds an AuthorizationToken from types.AuthorizationData. | ||
func NewToken(authData *types.AuthorizationData) (*AuthorizationToken, error) { | ||
token := *authData.AuthorizationToken | ||
if data, err := base64.StdEncoding.DecodeString(token); err != nil { | ||
return nil, err | ||
} else if parts := strings.SplitN(string(data), ":", 2); len(parts) != 2 { | ||
return nil, InvalidTokenError | ||
} else { | ||
return &AuthorizationToken{*authData, &parts[0], &parts[1]}, nil | ||
} | ||
} | ||
|
||
// GetToken obtains an AuthorizationToken from AWS ECR client using the passed context and options | ||
func GetToken(context context.Context, optsFns ...func(*config.LoadOptions) error) (*AuthorizationToken, error) { | ||
if client, err := getClient(context, optsFns...); err != nil { | ||
return nil, err | ||
} else if authData, err := getAuthData(context, client); err != nil { | ||
return nil, err | ||
} else { | ||
return NewToken(authData) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package ecr | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"errors" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr/types" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func must[T any](v T, err error) T { | ||
if err != nil { | ||
panic(err) | ||
} | ||
return v | ||
} | ||
func ind[T any](val T) *T { return &val } | ||
|
||
type mockECRClient func(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) | ||
|
||
func (m mockECRClient) GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return m(ctx, params, optFns...) | ||
} | ||
|
||
func TestGetAuthData(t *testing.T) { | ||
t.Run("Success", func(t *testing.T) { | ||
expected := types.AuthorizationData{AuthorizationToken: ind("Zm9vOmJhcg==")} | ||
client := mockECRClient(func(context.Context, *ecr.GetAuthorizationTokenInput, ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return &ecr.GetAuthorizationTokenOutput{AuthorizationData: []types.AuthorizationData{expected}}, nil | ||
}) | ||
|
||
authData, err := getAuthData(context.TODO(), client) | ||
if err != nil { | ||
t.Fatalf("expected nil error, got %v", err) | ||
} | ||
if authData == nil { | ||
t.Fatalf("expected authorization data, got nil") | ||
} | ||
|
||
if expected.AuthorizationToken != authData.AuthorizationToken { | ||
t.Errorf("expected authorization token %v, got %v", expected.AuthorizationToken, authData.AuthorizationToken) | ||
} | ||
}) | ||
|
||
testError := errors.New("my error") | ||
fail := map[string]struct { | ||
client ecrTokenClient | ||
expected error | ||
}{ | ||
"Error": { | ||
mockECRClient(func(context.Context, *ecr.GetAuthorizationTokenInput, ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return nil, testError | ||
}), | ||
testError, | ||
}, | ||
"Invalid(EmptyAuthorizationDataArray)": { | ||
mockECRClient(func(context.Context, *ecr.GetAuthorizationTokenInput, ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return &ecr.GetAuthorizationTokenOutput{AuthorizationData: []types.AuthorizationData{}}, nil | ||
}), | ||
UnexpectedResponseError, | ||
}, | ||
"Invalid(AuthorizationDataArrayTooLong)": { | ||
mockECRClient(func(context.Context, *ecr.GetAuthorizationTokenInput, ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return &ecr.GetAuthorizationTokenOutput{AuthorizationData: []types.AuthorizationData{{AuthorizationToken: ind("Zm9vOmJhcg==")}, {AuthorizationToken: ind("Zm9vOmJhcjpiYXo=")}}}, nil | ||
}), | ||
UnexpectedResponseError, | ||
}, | ||
} | ||
for name, tt := range fail { | ||
t.Run(name, func(t *testing.T) { | ||
client, expected := tt.client, tt.expected | ||
authData, err := getAuthData(context.TODO(), client) | ||
if authData != nil { | ||
t.Fatalf("expected nil authorization data, got %v", authData) | ||
} | ||
if !errors.Is(err, expected) { | ||
t.Errorf("expected error %v, got %v", expected, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestNewToken(t *testing.T) { | ||
success := map[string]struct { | ||
input *types.AuthorizationData | ||
expected [2]string | ||
}{ | ||
"Success(foo:bar)": { | ||
&types.AuthorizationData{AuthorizationToken: ind("Zm9vOmJhcg=="), ExpiresAt: ind(must(time.Parse(time.RFC3339, "2024-04-18T13:18:00Z")))}, | ||
[2]string{"foo", "bar"}, | ||
}, | ||
"Success(foo:bar:baz)": { | ||
&types.AuthorizationData{AuthorizationToken: ind("Zm9vOmJhcjpiYXo="), ExpiresAt: ind(must(time.Parse(time.RFC3339, "2024-04-18T13:18:00Z")))}, | ||
[2]string{"foo", "bar:baz"}, | ||
}, | ||
} | ||
|
||
for name, tt := range success { | ||
t.Run(name, func(t *testing.T) { | ||
input, username, password := tt.input, tt.expected[0], tt.expected[1] | ||
token, err := NewToken(input) | ||
if err != nil { | ||
t.Fatalf("expected nil error, got %v", err) | ||
} | ||
if token == nil { | ||
t.Fatalf("expected token, got nil") | ||
} | ||
|
||
if input.AuthorizationToken != token.AuthorizationToken { | ||
t.Errorf("expected authorization token %v, got %v", input.AuthorizationToken, token.AuthorizationToken) | ||
} | ||
if input.ExpiresAt != token.ExpiresAt { | ||
t.Errorf("expected expiration %s, got %s", input.ExpiresAt, token.ExpiresAt) | ||
} | ||
if input.ProxyEndpoint != token.ProxyEndpoint { | ||
t.Errorf("expected proxy endpoint %v, got %v", input.ProxyEndpoint, token.ProxyEndpoint) | ||
} | ||
if input.AuthorizationToken != token.AuthorizationToken { | ||
t.Errorf("expected authorization token %v, got %v", input.AuthorizationToken, token.AuthorizationToken) | ||
} | ||
if username != *token.Username { | ||
t.Errorf("expected username %s, got %s", username, *token.Username) | ||
} | ||
if password != *token.Password { | ||
t.Errorf("expected password %s, got %s", password, *token.Password) | ||
} | ||
}) | ||
} | ||
|
||
fail := map[string]struct { | ||
input *types.AuthorizationData | ||
expected error | ||
}{ | ||
"Invalid(CorruptInput)": { | ||
&types.AuthorizationData{AuthorizationToken: ind("__NOT_A_BASE64_STRING!"), ExpiresAt: ind(must(time.Parse(time.RFC3339, "2024-04-18T13:18:00Z")))}, | ||
base64.CorruptInputError(0), | ||
}, | ||
"Invalid(`foo`)": { | ||
&types.AuthorizationData{AuthorizationToken: ind("Zm9v"), ExpiresAt: ind(must(time.Parse(time.RFC3339, "2024-04-18T13:18:00Z")))}, | ||
InvalidTokenError, | ||
}, | ||
} | ||
for name, tt := range fail { | ||
t.Run(name, func(t *testing.T) { | ||
input, expected := tt.input, tt.expected | ||
token, err := NewToken(input) | ||
if token != nil { | ||
t.Fatalf("expected nil token, got %v", token) | ||
} | ||
if !errors.Is(err, expected) { | ||
t.Errorf("expected error %v, got %v", expected, err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters