Files
mesh-catalog/modules/keycloak/cmd/keycloak-provider/moved_test.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

193 lines
6.7 KiB
Go

package main
import (
"context"
"encoding/json"
"os"
"path/filepath"
"reflect"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/provider"
"git.novox.be/novox/mesh-sdk/go/provider/harness"
)
const testIssuer = "https://id.example.org/realms/Novox"
// keycloak's own part of the moved path (issue 849): the shared driving of it is proven in the SDK's harness package.
// What a consumer is told is what it contributes, with the names the controller composed for it over them (issue 792).
func TestAConsumersValuesAreWhatItContributesWithItsComposedNames(t *testing.T) {
got := valuesOf(provider.Entry{Contributes: map[string]any{"label": "x", "name": "old"},
Names: map[string]string{"name": "x.example.org"}})
if want := map[string]any{"label": "x", "name": "x.example.org"}; !reflect.DeepEqual(got, want) {
t.Fatalf("%v, want %v", got, want)
}
if served := servedValues(testIssuer); served["issuer"] != testIssuer {
t.Fatalf("%v", served)
}
}
// Where the file is not there, keycloak takes the grant directory, as before; where it is, the helper.
func TestTheGrantPathIsTakenWhereTheConsumersFileIsAbsent(t *testing.T) {
dir := t.TempDir()
at := filepath.Join(dir, "consumers.json")
env := func(k string) string {
if k == "MESH_CONSUMERS_FILE" {
return at
}
return ""
}
if got := handedConsumersFile(env, os.Stat); got != "" {
t.Fatalf("took %q for a file that is not there", got)
}
if got := handedConsumersFile(func(string) string { return "" }, os.Stat); got != "" {
t.Fatalf("took %q with no environment", got)
}
if err := os.WriteFile(at, []byte("[]"), 0o600); err != nil {
t.Fatal(err)
}
if got := handedConsumersFile(env, os.Stat); got != at {
t.Fatalf("took %q, want %q", got, at)
}
}
// The grant path restarts itself when the consumers file is first filled.
func TestTheGrantPathLeavesWhenTheConsumersFileIsFilled(t *testing.T) {
at := filepath.Join(t.TempDir(), "consumers.json")
done := make(chan struct{})
go exitWhenFilled(at, os.Stat, time.Millisecond, func() { close(done) })
select {
case <-done:
t.Fatal("left before the file was there")
case <-time.After(20 * time.Millisecond):
}
if err := os.WriteFile(at, []byte("[]"), 0o600); err != nil {
t.Fatal(err)
}
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("did not leave when the file was filled")
}
}
// bucketBus is the helper's bus over in-memory buckets.
type bucketBus struct {
mu sync.Mutex
buckets map[string]map[string][]byte
}
func (b *bucketBus) Watch(_ context.Context, bucket string, handle func(string, []byte)) error {
b.mu.Lock()
defer b.mu.Unlock()
for k, v := range b.buckets[bucket] {
handle(k, v)
}
return nil
}
func (b *bucketBus) Put(_ context.Context, bucket, key string, value []byte) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.buckets[bucket] == nil {
b.buckets[bucket] = map[string][]byte{}
}
b.buckets[bucket][key] = append([]byte(nil), value...)
return nil
}
func (b *bucketBus) Delete(_ context.Context, bucket, key string) error {
b.mu.Lock()
defer b.mu.Unlock()
delete(b.buckets[bucket], key)
return nil
}
// grafanaContributes and grafanaNames are what grafana's plan entry carries for keycloak: its contribution, and the
// names the controller composed for it (issues 776, 792).
var grafanaContributes = map[string]any{"label": "grafana", "endpoint": "web", "callback": "/login/generic_oauth"}
var grafanaNames = map[string]string{"name": "grafana.example.org", "internal-name": "grafana.home.internal"}
// Against a Keycloak, both paths leave the same client: the grant file through the harness, and the consumers' file
// through the SDK's moved path with keycloak's spec (issue 849).
func TestBothPathsLeaveTheSameClientInKeycloak(t *testing.T) {
grafana := provider.ConsumerIdentity("home", "grafana")
values := map[string]any{"name": grafanaNames["name"], "internal-name": grafanaNames["internal-name"]}
for k, v := range grafanaContributes {
values[k] = v
}
now := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC)
made := func(through func(p provisioner)) Rep {
f, o := oidcWorld(t)
through(provisioner{clients: o, announce: func(string, map[string]any) {}, log: func(string, ...any) {}})
return only(t, f, grafana)
}
viaGrant := made(func(p provisioner) {
dir := t.TempDir()
secret := filepath.Join(dir, grafana+".secret")
if err := os.WriteFile(secret, []byte("pw-"+grafana+"\n"), 0o600); err != nil {
t.Fatal(err)
}
raw, _ := json.Marshal(map[string]any{"requirement": ProvisionName, "given": []map[string]any{
{"as": grafana, "node": "home", "values": values, "secret": secret}}})
receives := filepath.Join(dir, "mesh.json")
if err := os.WriteFile(receives, raw, 0o600); err != nil {
t.Fatal(err)
}
h := &harness.Harness{Resource: ProvisionName, Receives: receives, Adapter: p,
Now: func() time.Time { return now }, Log: func(string, ...any) {}}
h.Reconcile(ctx)
})
viaFile := made(func(p provisioner) {
list, _ := json.Marshal([]provider.ConsumerCredential{{Consumer: "home", Module: "grafana",
Credential: provider.PairName("novox", ProvisionName, "home", "grafana", ""), Value: "pw-" + grafana}})
file := filepath.Join(t.TempDir(), "consumers.json")
if err := os.WriteFile(file, list, 0o600); err != nil {
t.Fatal(err)
}
h := &harness.Harness{Resource: ProvisionName, Adapter: p, Node: "novox",
Now: func() time.Time { return now }, Log: func(string, ...any) {}}
run, _, _, err := moved(file, h, testIssuer)
if err != nil {
t.Fatal(err)
}
bus := &bucketBus{buckets: map[string]map[string][]byte{}}
seatPlan(bus, "home", "grafana", map[string]any{
"provisions": []any{map[string]any{"provision": ProvisionName, "provider": "novox",
"provider-module": "keycloak", "contributes": grafanaContributes, "names": grafanaNames}}})
pr := provider.New(provider.Options{Node: "novox", Module: "keycloak", Bus: bus,
Vault: provider.ConsumersFile{Paths: []string{file}},
Offers: map[string]provider.Offer{ProvisionName: {Credential: provider.PerConsumer,
Serves: servedValues(testIssuer)}},
Make: run.Make, Gone: run.Gone, Logf: func(string, ...any) {}})
out, err := pr.Pass(ctx)
if err != nil || len(out) != 1 || out[0].Status != provider.Answered {
t.Fatalf("%+v %v", out, err)
}
})
// Keycloak's own ids differ between two runs; nothing the mesh sets does.
for _, c := range []Rep{viaGrant, viaFile} {
delete(c, "id")
raw, _ := json.Marshal(c)
var again Rep
_ = json.Unmarshal(raw, &again)
for k := range c {
delete(c, k)
}
for k, v := range again {
c[k] = v
}
if mappers, ok := c["protocolMappers"].([]any); ok {
for _, m := range mappers {
delete(m.(map[string]any), "id")
}
}
}
if !reflect.DeepEqual(viaGrant, viaFile) {
t.Fatalf("through the file %v, through the grant %v", viaFile, viaGrant)
}
}