| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | "strings" |
| 9 | "sync" |
| 10 | "testing" |
| 11 | "time" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/hostactors" |
| 14 | "github.com/carlosframework/platform/internal/hostops" |
| 15 | "github.com/carlosframework/platform/internal/release" |
| 16 | ) |
| 17 | |
| 18 | // noMembers is a membership reader that is never asked. |
| 19 | type noMembers struct{} |
| 20 | |
| 21 | func (noMembers) Member(context.Context, string, string) (hostactors.Membership, error) { |
| 22 | return hostactors.Membership{}, errors.New("not asked") |
| 23 | } |
| 24 | |
| 25 | func TestEnrollPassIsBuiltFromTheClaimAuthority(t *testing.T) { |
| 26 | pub, key, _ := ed25519.GenerateKey(nil) |
| 27 | a := &hostops.ProductionAuthority{Directory: hostactors.Directory{Store: release.NewDirStore(t.TempDir()), PublicKey: pub, Bucket: "bucket"}, |
| 28 | Signer: release.RootSigner(key), Namespace: "platform"} |
| 29 | if _, err := newAutoEnroller(nil, noMembers{}); !errors.Is(err, hostops.ErrContract) { |
| 30 | t.Fatalf("no authority: %v", err) |
| 31 | } |
| 32 | if _, err := newAutoEnroller(a, nil); !errors.Is(err, hostops.ErrContract) { |
| 33 | t.Fatalf("no membership reader: %v", err) |
| 34 | } |
| 35 | e, err := newAutoEnroller(a, noMembers{}) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | if e.Namespace != "platform" || e.Pool != "default" || e.Patience != enrollPatience || len(e.Certs) == 0 || e.Directory.Bucket != "bucket" || e.Members != (noMembers{}) || e.Logf == nil { |
| 40 | t.Fatalf("enroller %+v", e) |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | // A pass that cannot read the roster is logged on its first failure and |
| 45 | // every tenth, not every ten seconds, and the loop ends with its context. |
| 46 | func TestEnrollPassLogsFailuresSparingly(t *testing.T) { |
| 47 | pub, key, _ := ed25519.GenerateKey(nil) |
| 48 | e := &hostactors.AutoEnroller{Directory: hostactors.Directory{Store: release.NewDirStore(t.TempDir()), PublicKey: pub, Bucket: "bucket"}, |
| 49 | Signer: release.RootSigner(key), Namespace: "platform", Pool: "default", Now: time.Now, Patience: time.Minute} |
| 50 | var mu sync.Mutex |
| 51 | var lines []string |
| 52 | logf := func(format string, args ...any) { |
| 53 | mu.Lock() |
| 54 | lines = append(lines, fmt.Sprintf(format, args...)) |
| 55 | mu.Unlock() |
| 56 | } |
| 57 | ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) |
| 58 | defer cancel() |
| 59 | runEnrollPass(ctx, e, 2*time.Millisecond, logf) |
| 60 | mu.Lock() |
| 61 | defer mu.Unlock() |
| 62 | if len(lines) < 2 || !strings.Contains(lines[0], "armed") { |
| 63 | t.Fatalf("lines %q", lines) |
| 64 | } |
| 65 | for i, l := range lines[1:] { |
| 66 | var n int |
| 67 | if _, err := fmt.Sscanf(l[strings.Index(l, "#"):], "#%d", &n); err != nil || (n != 1 && n%10 != 0) || (i == 0 && n != 1) { |
| 68 | t.Fatalf("logged %q", l) |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |