Check that what a node says when it joins is what this mesh reads
The enrolment request is a struct in each repository. A node now reports a third key — the one its secrets are sealed to — and that wiring had unit tests on each side and had never been run across the join. A field renamed on one side fails silently: enrolment succeeds, the key is absent, and the node looks joined until the first thing sealed to it cannot be opened, by which point nobody is looking at enrolment. So the host's suite writes a real request and this one reads it, the same way the declaration check already runs in the other direction. Both skip with a reason when the neighbour is not checked out. It does more than compare shapes: it seals something to the key that arrived and opens it with the private half the host kept. Confirmed to fail three ways — a renamed field, a value that is not a key, and a key that is present, correctly named and simply somebody else's. Only the last needs the sealing step, and it is the one a shape check would pass. Also `inventory.ForTest`, because the check lives beside the link and a second copy of the throwaway-database helper would be a second thing to keep true.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"github.com/novox/mesh-control/internal/store"
|
||||
)
|
||||
|
||||
// ForTest is a fresh inventory in a database of its own, dropped when the test ends.
|
||||
//
|
||||
// Exported because the check that this mesh reads what a node sends lives beside the link, and a
|
||||
// second copy of this would be a second thing to keep true. It takes a *testing.T, so it cannot
|
||||
// be called from anything that is not a test.
|
||||
func ForTest(t *testing.T) *Inventory {
|
||||
t.Helper()
|
||||
admin := os.Getenv("MESH_TEST_POSTGRES")
|
||||
if admin == "" {
|
||||
t.Skip("no MESH_TEST_POSTGRES; run `make check` to raise one")
|
||||
}
|
||||
name := fmt.Sprintf("inv_%d_%s", time.Now().UnixNano()%1_000_000,
|
||||
strings.ToLower(strings.NewReplacer("/", "", "-", "").Replace(t.Name())))
|
||||
if len(name) > 60 {
|
||||
name = name[:60]
|
||||
}
|
||||
|
||||
conn, err := pgx.Connect(t.Context(), admin)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot reach the test PostgreSQL: %v", err)
|
||||
}
|
||||
if _, err := conn.Exec(t.Context(), "create database "+name); err != nil {
|
||||
t.Fatalf("cannot create %s: %v", name, err)
|
||||
}
|
||||
conn.Close(t.Context())
|
||||
|
||||
cut := strings.LastIndex(admin, "/")
|
||||
t.Setenv(store.Variable(Name), admin[:cut]+"/"+name+"?sslmode=disable")
|
||||
|
||||
inv, err := Open(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
inv.Close()
|
||||
c, err := pgx.Connect(context.Background(), admin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close(context.Background())
|
||||
_, _ = c.Exec(context.Background(), "drop database if exists "+name+" with (force)")
|
||||
})
|
||||
if err := inv.Ready(t.Context(), 20*time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
migrations, err := Migrations()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.store.Migrate(t.Context(), migrations); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return inv
|
||||
}
|
||||
@@ -3,15 +3,10 @@ package inventory
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/novox/mesh-control/internal/store"
|
||||
)
|
||||
|
||||
// Against a real PostgreSQL, for the reason novox/hq ADR 0017 gives: what is being tested here is
|
||||
@@ -19,55 +14,10 @@ import (
|
||||
// two racing redemptions cannot both spend, a cascade that leaves no token behind. A fake would
|
||||
// assert that the fake enforces them.
|
||||
|
||||
// fresh is a database of this test's own, made and dropped around it.
|
||||
func fresh(t *testing.T) *Inventory {
|
||||
t.Helper()
|
||||
admin := os.Getenv("MESH_TEST_POSTGRES")
|
||||
if admin == "" {
|
||||
t.Skip("no MESH_TEST_POSTGRES; run `make check` to raise one")
|
||||
}
|
||||
name := fmt.Sprintf("inv_%d_%s", time.Now().UnixNano()%1_000_000,
|
||||
strings.ToLower(strings.NewReplacer("/", "", "-", "").Replace(t.Name())))
|
||||
if len(name) > 60 {
|
||||
name = name[:60]
|
||||
}
|
||||
|
||||
conn, err := pgx.Connect(t.Context(), admin)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot reach the test PostgreSQL: %v", err)
|
||||
}
|
||||
if _, err := conn.Exec(t.Context(), "create database "+name); err != nil {
|
||||
t.Fatalf("cannot create %s: %v", name, err)
|
||||
}
|
||||
conn.Close(t.Context())
|
||||
|
||||
cut := strings.LastIndex(admin, "/")
|
||||
t.Setenv(store.Variable(Name), admin[:cut]+"/"+name+"?sslmode=disable")
|
||||
|
||||
inv, err := Open(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
inv.Close()
|
||||
c, err := pgx.Connect(context.Background(), admin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close(context.Background())
|
||||
_, _ = c.Exec(context.Background(), "drop database if exists "+name+" with (force)")
|
||||
})
|
||||
if err := inv.Ready(t.Context(), 20*time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
migrations, err := Migrations()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.store.Migrate(t.Context(), migrations); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return inv
|
||||
return ForTest(t)
|
||||
}
|
||||
|
||||
func TestANodeRecordRoundTrips(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user