mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 08:02:04 +00:00
* feat: compare the decoded event body's fields and not only its type, fill arrays so every uuid carries a real value instead of the zero one a codec could drop unnoticed, and decode once in a process that has never encoded, because production is four processes and one of them only ever reads what another wrote * feat: register the union body types at package load instead of on first schema build, which is what a process that only ever decodes never reached, so every worker command arrived as a map keyed by its branch name, went through the JSON fallback, and became a struct with every field zero and no error anywhere
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hamba/avro/v2"
|
|
)
|
|
|
|
// Production is four processes, and one of them only ever decodes what another
|
|
// encoded. Every other test here encodes and decodes in one process, which
|
|
// registers the union's Go types as a side effect of building the schema, so
|
|
// the decode half was never tested from a cold start.
|
|
//
|
|
// That gap is why an Avro cutover failed: the worker decoded ADD_EMAIL into a
|
|
// body with every field empty, reported "Unsupported email provider" for all 39
|
|
// mailboxes, and never logged a decode error. Silence, not a failure.
|
|
//
|
|
// So the decoder here is a child process that has never encoded anything.
|
|
const (
|
|
decodeHelperEnv = "WARMBLY_DECODE_HELPER"
|
|
decodeSchemaEnv = "WARMBLY_DECODE_SCHEMA"
|
|
)
|
|
|
|
func TestDecodeInAProcessThatHasNeverEncoded(t *testing.T) {
|
|
if payload := os.Getenv(decodeHelperEnv); payload != "" {
|
|
runDecodeHelper(t, payload)
|
|
return
|
|
}
|
|
|
|
schema := WorkerEvent{}.Schema()
|
|
in := WorkerEvent{Type: WorkerEventTypeAddEmail, Body: sample(WorkerEventBodies[WorkerEventTypeAddEmail])}
|
|
raw, err := avro.Marshal(schema, in)
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
|
|
cmd := exec.Command(os.Args[0], "-test.run", "TestDecodeInAProcessThatHasNeverEncoded", "-test.v")
|
|
cmd.Env = append(os.Environ(),
|
|
decodeHelperEnv+"="+base64.StdEncoding.EncodeToString(raw),
|
|
// The schema travels as JSON, the way a registry hands one back. The
|
|
// child must not build its own, because building one registers the
|
|
// union's Go types and that is the side effect under test.
|
|
decodeSchemaEnv+"="+schema.String(),
|
|
)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("decoder process failed: %v\n%s", err, out)
|
|
}
|
|
if !strings.Contains(string(out), "DECODED-OK") {
|
|
t.Fatalf("decoder process did not confirm the body type:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// runDecodeHelper is the child, and the whole point of it is what it does not
|
|
// do: it never calls Schema(). The codec's decode path does not either, because
|
|
// it parses the writer's schema out of the registry, so a process that has only
|
|
// ever consumed has nothing that would have run avro.Register.
|
|
func runDecodeHelper(t *testing.T, encoded string) {
|
|
raw, err := base64.StdEncoding.DecodeString(encoded)
|
|
if err != nil {
|
|
t.Fatalf("helper: payload: %v", err)
|
|
}
|
|
schema, err := avro.Parse(os.Getenv(decodeSchemaEnv))
|
|
if err != nil {
|
|
t.Fatalf("helper: schema: %v", err)
|
|
}
|
|
|
|
var out WorkerEvent
|
|
if err := avro.Unmarshal(schema, raw, &out); err != nil {
|
|
t.Fatalf("helper: decode: %v", err)
|
|
}
|
|
want := reflect.TypeOf(sample(WorkerEventBodies[WorkerEventTypeAddEmail]))
|
|
if got := reflect.TypeOf(out.Body); got != want {
|
|
t.Fatalf("helper: body decoded as %v, want %v", got, want)
|
|
}
|
|
body, ok := out.Body.(*AddWorkerEmail)
|
|
if !ok {
|
|
t.Fatalf("helper: body is %T", out.Body)
|
|
}
|
|
if body.Type == "" || body.ID.String() == "00000000-0000-0000-0000-000000000000" {
|
|
t.Fatalf("helper: body decoded empty: provider=%q id=%s", body.Type, body.ID)
|
|
}
|
|
t.Log("DECODED-OK")
|
|
}
|