refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
162
modules/lagoon/fill_test.go
Normal file
162
modules/lagoon/fill_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package lagoon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fillFixture struct {
|
||||
Name string `gorm:"column:name"`
|
||||
CollectionID uint `gorm:"column:collection_id"`
|
||||
Notes *string `gorm:"column:notes"`
|
||||
}
|
||||
|
||||
func (fillFixture) Fillable() []string { return []string{"name", "notes"} }
|
||||
func (fillFixture) Hidden() []string { return []string{"collection_id"} }
|
||||
|
||||
var (
|
||||
_ HasFillable = fillFixture{}
|
||||
_ HasHidden = fillFixture{}
|
||||
)
|
||||
|
||||
func TestFillAllowList(t *testing.T) {
|
||||
var row fillFixture
|
||||
row.CollectionID = 3
|
||||
err := Fill(&row, []string{"name"}, map[string]any{
|
||||
"name": "x",
|
||||
"collection_id": uint(9),
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row.Name != "x" {
|
||||
t.Fatalf("name = %q", row.Name)
|
||||
}
|
||||
if row.CollectionID != 3 {
|
||||
t.Fatalf("collection_id mutated to %d", row.CollectionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillDroppedKeyLogsOnce(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
prev := slog.Default()
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelWarn})))
|
||||
defer slog.SetDefault(prev)
|
||||
|
||||
var row fillFixture
|
||||
requested := map[string]any{"name": "once", "collection_id": uint(1)}
|
||||
if err := Fill(&row, []string{"name"}, requested, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Fill(&row, []string{"name"}, requested, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log := buf.String()
|
||||
if strings.Count(log, "collection_id") != 1 {
|
||||
t.Fatalf("dropped key should log once, got %q", log)
|
||||
}
|
||||
if !strings.Contains(log, "lagoon: dropped non-fillable key") {
|
||||
t.Fatalf("missing warn message: %q", log)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillProductionSilent(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
prev := slog.Default()
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelWarn})))
|
||||
defer slog.SetDefault(prev)
|
||||
|
||||
var row fillFixture
|
||||
if err := Fill(&row, []string{"name"}, map[string]any{
|
||||
"name": "prod",
|
||||
"unknown_field": true,
|
||||
}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if buf.Len() != 0 {
|
||||
t.Fatalf("production fill must be silent, got %q", buf.String())
|
||||
}
|
||||
if row.Name != "prod" {
|
||||
t.Fatalf("name = %q", row.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillNilPointerClearsNullable(t *testing.T) {
|
||||
existing := "keep"
|
||||
row := fillFixture{Notes: &existing}
|
||||
if err := Fill(&row, []string{"notes"}, map[string]any{"notes": nil}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row.Notes != nil {
|
||||
t.Fatalf("notes = %v, want nil", row.Notes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillDroppedKeyNeverErrors(t *testing.T) {
|
||||
var row fillFixture
|
||||
if err := Fill(&row, []string{"name"}, map[string]any{"collection_id": uint(9), "nope": 1}, true); err != nil {
|
||||
t.Fatalf("dropped keys must not error: %v", err)
|
||||
}
|
||||
if row.CollectionID != 0 {
|
||||
t.Fatalf("collection_id = %d", row.CollectionID)
|
||||
}
|
||||
}
|
||||
|
||||
type fillSecretFixture struct {
|
||||
Name string `gorm:"column:name"`
|
||||
APIKey Encrypted `gorm:"column:api_key" json:"-"`
|
||||
Token *Encrypted `gorm:"column:token" json:"-"`
|
||||
}
|
||||
|
||||
func TestFillEncryptedTakesPlaintext(t *testing.T) {
|
||||
if err := PublishEncryptionKeys(nil, bytes.Repeat([]byte("F"), 32), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
allowed := []string{"name", "api_key", "token"}
|
||||
var row fillSecretFixture
|
||||
if err := Fill(&row, allowed, map[string]any{"api_key": "sk-plain", "token": "tok-plain"}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row.APIKey.Reveal() != "sk-plain" {
|
||||
t.Fatalf("api_key Reveal = %q", row.APIKey.Reveal())
|
||||
}
|
||||
if row.Token == nil || row.Token.Reveal() != "tok-plain" {
|
||||
t.Fatalf("token = %v", row.Token)
|
||||
}
|
||||
|
||||
// Another row's ciphertext is stored as literal text, never decrypted.
|
||||
stolen, err := NewEncrypted("victim-secret").Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ciphertext, ok := stolen.(string)
|
||||
if !ok {
|
||||
t.Fatalf("ciphertext driver value is %T", stolen)
|
||||
}
|
||||
var thief fillSecretFixture
|
||||
if err := Fill(&thief, allowed, map[string]any{"api_key": ciphertext}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if thief.APIKey.Reveal() != ciphertext {
|
||||
t.Fatal("ciphertext in a request must not be decrypted into the victim's plaintext")
|
||||
}
|
||||
|
||||
for _, bad := range []any{42, true, map[string]any{"x": 1}, []byte("raw")} {
|
||||
var r fillSecretFixture
|
||||
if err := Fill(&r, allowed, map[string]any{"api_key": bad}, true); err == nil {
|
||||
t.Fatalf("api_key=%T must be rejected", bad)
|
||||
}
|
||||
if r.APIKey.Reveal() != "" {
|
||||
t.Fatalf("api_key=%T left a value behind", bad)
|
||||
}
|
||||
}
|
||||
|
||||
if err := Fill(&row, allowed, map[string]any{"api_key": nil, "token": nil}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row.APIKey.Reveal() != "" || row.Token != nil {
|
||||
t.Fatalf("nil must clear: api_key=%q token=%v", row.APIKey.Reveal(), row.Token)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user