Add testing
This commit is contained in:
parent
3233f8c27f
commit
06e6d457da
14 changed files with 273 additions and 5 deletions
|
@ -18,6 +18,8 @@
|
||||||
_or_ return an error defined (as public variable) in that package.
|
_or_ return an error defined (as public variable) in that package.
|
||||||
Example: `other.Error("auth", "failed to do something important", originalError)`
|
Example: `other.Error("auth", "failed to do something important", originalError)`
|
||||||
- Returned http errors must use `git.mstar.dev/mstar/goutils/webutils.ProblemDetails(StatusOnly)`.
|
- Returned http errors must use `git.mstar.dev/mstar/goutils/webutils.ProblemDetails(StatusOnly)`.
|
||||||
|
- Every function must either have a test or a doc comment explaining
|
||||||
|
why it doesn't have a test
|
||||||
|
|
||||||
## JS/TS
|
## JS/TS
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ type FSWrapper struct {
|
||||||
log bool
|
log bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile time interface implementation assertion
|
||||||
|
var _ fs.FS = &FSWrapper{}
|
||||||
|
|
||||||
|
// No test, as no processing happens, only wrapping the arguments in a struct
|
||||||
func NewFSWrapper(wraps fs.FS, appends string, logAccess bool) *FSWrapper {
|
func NewFSWrapper(wraps fs.FS, appends string, logAccess bool) *FSWrapper {
|
||||||
return &FSWrapper{
|
return &FSWrapper{
|
||||||
wrapped: wraps,
|
wrapped: wraps,
|
||||||
|
@ -22,11 +26,12 @@ func NewFSWrapper(wraps fs.FS, appends string, logAccess bool) *FSWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FSWrapper) Open(name string) (fs.File, error) {
|
// Pretty sure this can't be reliably tested
|
||||||
res, err := fs.wrapped.Open(fs.toAdd + name)
|
func (fw *FSWrapper) Open(name string) (fs.File, error) {
|
||||||
if fs.log {
|
res, err := fw.wrapped.Open(fw.toAdd + name)
|
||||||
|
if fw.log {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("prefix", fs.toAdd).
|
Str("prefix", fw.toAdd).
|
||||||
Str("filename", name).
|
Str("filename", name).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("fswrapper: File access result")
|
Msg("fswrapper: File access result")
|
||||||
|
|
29
shared/fswrapper_test.go
Normal file
29
shared/fswrapper_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFSWrapper_Open(t *testing.T) {
|
||||||
|
rootFs := os.DirFS("/")
|
||||||
|
wrapper := NewFSWrapper(rootFs, "etc/", false)
|
||||||
|
f, err := wrapper.Open("hostname")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open /etc/hostname: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
data, err := os.ReadFile("/etc/hostname")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read with full path: %v", err)
|
||||||
|
}
|
||||||
|
wrappedData, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read from wrapped file: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(wrappedData, data) {
|
||||||
|
t.Fatal("file contents are different")
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ package shared
|
||||||
import "golang.org/x/sys/unix"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
// Copied from https://stackoverflow.com/a/20026945 and https://stackoverflow.com/a/49148866
|
// Copied from https://stackoverflow.com/a/20026945 and https://stackoverflow.com/a/49148866
|
||||||
|
// Not testable as host environment could be just about anything
|
||||||
|
// and this function is for testing the host environment
|
||||||
func IsWritable(path string) bool {
|
func IsWritable(path string) bool {
|
||||||
return unix.Access(path, unix.W_OK) == nil
|
return unix.Access(path, unix.W_OK) == nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package shared
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
// Copied from https://stackoverflow.com/a/20026945 and https://stackoverflow.com/a/49148866
|
// Copied from https://stackoverflow.com/a/20026945 and https://stackoverflow.com/a/49148866
|
||||||
|
// Not testable as host environment could be just about anything
|
||||||
|
// and this function is for testing the host environment.
|
||||||
|
// Also, Windows is not supported. It may work, it may not, doesn't matter
|
||||||
func IsWritable(path string) bool {
|
func IsWritable(path string) bool {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
108
shared/signing_test.go
Normal file
108
shared/signing_test.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testStringToSign = "some random text that I made up at work and definitely has no other purpose"
|
||||||
|
|
||||||
|
func TestGenerateKeypairRSA(t *testing.T) {
|
||||||
|
publicBytes, privateBytes, err := GenerateKeypair(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generation failed: %v", err)
|
||||||
|
}
|
||||||
|
publicUntyped, err := x509.ParsePKIXPublicKey(publicBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing public bytes into key failed: %v", err)
|
||||||
|
}
|
||||||
|
public, ok := publicUntyped.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("asserting public key as rsa.PublicKey failed")
|
||||||
|
}
|
||||||
|
private, err := x509.ParsePKCS1PrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing private bytes into key failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := private.Validate(); err != nil {
|
||||||
|
t.Fatalf("validation of private key failed: %v", err)
|
||||||
|
}
|
||||||
|
genPublicRaw := private.Public()
|
||||||
|
genPublic, ok := genPublicRaw.(*rsa.PublicKey)
|
||||||
|
if !reflect.DeepEqual(*public, *genPublic) {
|
||||||
|
t.Fatal("public from generator and from private are different")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateKeypairED(t *testing.T) {
|
||||||
|
publicBytes, privateBytes, err := GenerateKeypair(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generation failed: %v", err)
|
||||||
|
}
|
||||||
|
publicUntyped, err := x509.ParsePKIXPublicKey(publicBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing public bytes into key failed: %v", err)
|
||||||
|
}
|
||||||
|
public, ok := publicUntyped.(ed25519.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("asserting public key as ed25519.PublicKey failed")
|
||||||
|
}
|
||||||
|
private := ed25519.PrivateKey(privateBytes)
|
||||||
|
pubGenRaw := private.Public()
|
||||||
|
pubGen, ok := pubGenRaw.(ed25519.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("public key generated by private key is not of type ed25519.PublicKey")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(pubGen, public) {
|
||||||
|
t.Fatal("public from generator and from private are different")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignRSA(t *testing.T) {
|
||||||
|
publicBytes, privateBytes, err := GenerateKeypair(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate RSA keypair: %v", err)
|
||||||
|
}
|
||||||
|
pubRaw, err := x509.ParsePKIXPublicKey(publicBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse public rsa key: %v", err)
|
||||||
|
}
|
||||||
|
pub, ok := pubRaw.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("parsed public key is not *rsa.PublicKey")
|
||||||
|
}
|
||||||
|
hash := sha256.Sum256([]byte(testStringToSign))
|
||||||
|
signed, err := Sign(testStringToSign, privateBytes, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to sign test string: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], signed) != nil {
|
||||||
|
t.Fatal("failed to verify")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyBytesToPem(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := KeyBytesToPem(tt.args.bytes, true); got != tt.want {
|
||||||
|
t.Errorf("KeyBytesToPem() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tagRegex = regexp.MustCompile(`#(\w+)`)
|
var tagRegex = regexp.MustCompile(`#([a-zA-Z0-9\-_]+)`)
|
||||||
|
|
||||||
func TagsFromText(text string) []string {
|
func TagsFromText(text string) []string {
|
||||||
matches := tagRegex.FindAllString(text, -1)
|
matches := tagRegex.FindAllString(text, -1)
|
||||||
|
|
34
shared/tagsFromString_test.go
Normal file
34
shared/tagsFromString_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTagsFromText(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No tags",
|
||||||
|
args: args{"Text with no tags whatsoever"},
|
||||||
|
want: []string{},
|
||||||
|
}, {
|
||||||
|
name: "one tag",
|
||||||
|
args: args{"Text with one simple #tag"},
|
||||||
|
want: []string{"tag"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := TagsFromText(tt.args.text); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("TagsFromText() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Not testable since it focuses entirely on the side effect of
|
||||||
|
// sending a log out
|
||||||
func Trace(l *zerolog.Logger) *zerolog.Logger {
|
func Trace(l *zerolog.Logger) *zerolog.Logger {
|
||||||
if e := l.Trace(); e.Enabled() {
|
if e := l.Trace(); e.Enabled() {
|
||||||
e.Caller(2).
|
e.Caller(2).
|
||||||
|
@ -12,6 +14,8 @@ func Trace(l *zerolog.Logger) *zerolog.Logger {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not testable since it focuses entirely on the side effect of
|
||||||
|
// sending a log out
|
||||||
func Untrace(l *zerolog.Logger) {
|
func Untrace(l *zerolog.Logger) {
|
||||||
if e := l.Trace(); e.Enabled() {
|
if e := l.Trace(); e.Enabled() {
|
||||||
e.Caller(2).
|
e.Caller(2).
|
||||||
|
|
|
@ -12,10 +12,16 @@ type ZerologGormAdapter struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile time interface implementation enforcement
|
||||||
|
var _ logger.Interface = &ZerologGormAdapter{}
|
||||||
|
|
||||||
|
// Not worth testing as just a wrapper for putting into a struct
|
||||||
func NewGormLogger(zerologger zerolog.Logger) *ZerologGormAdapter {
|
func NewGormLogger(zerologger zerolog.Logger) *ZerologGormAdapter {
|
||||||
return &ZerologGormAdapter{zerologger}
|
return &ZerologGormAdapter{zerologger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not testable since it focuses entirely on the side effect of
|
||||||
|
// configuring loglevel
|
||||||
func (g *ZerologGormAdapter) LogMode(newLevel logger.LogLevel) logger.Interface {
|
func (g *ZerologGormAdapter) LogMode(newLevel logger.LogLevel) logger.Interface {
|
||||||
switch newLevel {
|
switch newLevel {
|
||||||
case logger.Error:
|
case logger.Error:
|
||||||
|
@ -29,16 +35,23 @@ func (g *ZerologGormAdapter) LogMode(newLevel logger.LogLevel) logger.Interface
|
||||||
}
|
}
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) Info(ctx context.Context, format string, args ...any) {
|
func (g *ZerologGormAdapter) Info(ctx context.Context, format string, args ...any) {
|
||||||
g.logger.Info().Ctx(ctx).Msgf(format, args...)
|
g.logger.Info().Ctx(ctx).Msgf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) Warn(ctx context.Context, format string, args ...any) {
|
func (g *ZerologGormAdapter) Warn(ctx context.Context, format string, args ...any) {
|
||||||
g.logger.Warn().Ctx(ctx).Msgf(format, args...)
|
g.logger.Warn().Ctx(ctx).Msgf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) Error(ctx context.Context, format string, args ...any) {
|
func (g *ZerologGormAdapter) Error(ctx context.Context, format string, args ...any) {
|
||||||
g.logger.Error().Ctx(ctx).Msgf(format, args...)
|
g.logger.Error().Ctx(ctx).Msgf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) Trace(
|
func (g *ZerologGormAdapter) Trace(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
begin time.Time,
|
begin time.Time,
|
||||||
|
@ -55,10 +68,12 @@ func (g *ZerologGormAdapter) Trace(
|
||||||
Send()
|
Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) OverwriteLoggingLevel(new zerolog.Level) {
|
func (g *ZerologGormAdapter) OverwriteLoggingLevel(new zerolog.Level) {
|
||||||
g.logger = g.logger.Level(new)
|
g.logger = g.logger.Level(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not worth testing since only a wrapper around another function call
|
||||||
func (g *ZerologGormAdapter) OverwriteLogger(new zerolog.Logger) {
|
func (g *ZerologGormAdapter) OverwriteLogger(new zerolog.Logger) {
|
||||||
g.logger = new
|
g.logger = new
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package shared
|
||||||
|
|
||||||
import "github.com/rs/zerolog/log"
|
import "github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
// Stuff here is not actually used anymore in the new implementation
|
||||||
|
// thus also no tests needed
|
||||||
|
|
||||||
type ZerologWrapper struct{}
|
type ZerologWrapper struct{}
|
||||||
|
|
||||||
func (z *ZerologWrapper) Errorf(format string, args ...any) {
|
func (z *ZerologWrapper) Errorf(format string, args ...any) {
|
||||||
|
|
|
@ -24,14 +24,17 @@ type Note struct {
|
||||||
var _ shared.Clonable = &Note{}
|
var _ shared.Clonable = &Note{}
|
||||||
var _ shared.Sanitisable = &Note{}
|
var _ shared.Sanitisable = &Note{}
|
||||||
|
|
||||||
|
// No test, does nothing currently
|
||||||
func (note *Note) Sanitize() {
|
func (note *Note) Sanitize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No test, no data processing, only copy
|
||||||
func (note *Note) Clone() shared.Clonable {
|
func (note *Note) Clone() shared.Clonable {
|
||||||
tmp := *note
|
tmp := *note
|
||||||
return &tmp
|
return &tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No test, no data processing, only copy
|
||||||
func (n *Note) FromModel(m *models.Note) {
|
func (n *Note) FromModel(m *models.Note) {
|
||||||
n.ID = m.ID
|
n.ID = m.ID
|
||||||
n.CreatedAt = m.CreatedAt
|
n.CreatedAt = m.CreatedAt
|
||||||
|
|
|
@ -44,11 +44,13 @@ type User struct {
|
||||||
var _ shared.Sanitisable = &User{}
|
var _ shared.Sanitisable = &User{}
|
||||||
var _ shared.Clonable = &User{}
|
var _ shared.Clonable = &User{}
|
||||||
|
|
||||||
|
// No test due to no fancy processing, just setting values to constants
|
||||||
func (u *User) Sanitize() {
|
func (u *User) Sanitize() {
|
||||||
u.Verified = nil
|
u.Verified = nil
|
||||||
u.FinishedRegistration = nil
|
u.FinishedRegistration = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No test, no data processing, only copy
|
||||||
func (u *User) Clone() shared.Clonable {
|
func (u *User) Clone() shared.Clonable {
|
||||||
user := *u
|
user := *u
|
||||||
if u.IconId != nil {
|
if u.IconId != nil {
|
||||||
|
@ -83,6 +85,7 @@ func (u *User) Clone() shared.Clonable {
|
||||||
return &user
|
return &user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No test, no data processing, only copy
|
||||||
func (u *User) FromModel(m *models.User) {
|
func (u *User) FromModel(m *models.User) {
|
||||||
u.ID = m.ID
|
u.ID = m.ID
|
||||||
u.CreatedAt = m.CreatedAt
|
u.CreatedAt = m.CreatedAt
|
||||||
|
|
57
web/shared/client_test.go
Normal file
57
web/shared/client_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package webshared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testBody = `{
|
||||||
|
"key": "value",
|
||||||
|
"more-key": 21
|
||||||
|
}`
|
||||||
|
|
||||||
|
var testBodyHash = []byte("22a5173da554010ea25f7d2ae34032a434c3a883a55d65f34c3413fdc555bed3")
|
||||||
|
|
||||||
|
func TestSignRequest(t *testing.T) {
|
||||||
|
// TODO: Implement tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_applyBodyHash_WithBody(t *testing.T) {
|
||||||
|
var headers = make(http.Header, 0)
|
||||||
|
digest := "SHA-256=" + string(testBodyHash)
|
||||||
|
applyBodyHash(headers, []byte(testBody))
|
||||||
|
headerDigest := headers.Get("Digest")
|
||||||
|
if headerDigest != digest {
|
||||||
|
t.Fatalf("digests didn't match: header \"%v\" != precalc \"%v\"", headerDigest, digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRequest(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
method string
|
||||||
|
url string
|
||||||
|
body io.Reader
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *http.Request
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewRequest(tt.args.method, tt.args.url, tt.args.body)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NewRequest() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue