| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "io" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/hostclaim" |
| 12 | "github.com/carlosframework/platform/internal/hostops" |
| 13 | "github.com/carlosframework/platform/internal/release" |
| 14 | ) |
| 15 | |
| 16 | type plan15ConsoleStream struct { |
| 17 | release.Store |
| 18 | read, closed int |
| 19 | } |
| 20 | |
| 21 | func (s *plan15ConsoleStream) Get(ctx context.Context, key string) ([]byte, string, error) { |
| 22 | if strings.Contains(key, "control/host-origins/") { |
| 23 | panic("console wrapper buffered origin") |
| 24 | } |
| 25 | return s.Store.Get(ctx, key) |
| 26 | } |
| 27 | func (s *plan15ConsoleStream) Open(context.Context, string) (io.ReadCloser, string, error) { |
| 28 | return plan15ConsoleReader{s, strings.NewReader(strings.Repeat("x", 8192))}, "", nil |
| 29 | } |
| 30 | |
| 31 | type plan15ConsoleReader struct { |
| 32 | s *plan15ConsoleStream |
| 33 | r io.Reader |
| 34 | } |
| 35 | |
| 36 | func (r plan15ConsoleReader) Read(b []byte) (int, error) { |
| 37 | n, e := r.r.Read(b) |
| 38 | r.s.read += n |
| 39 | return n, e |
| 40 | } |
| 41 | func (r plan15ConsoleReader) Close() error { r.s.closed++; return nil } |
| 42 | |
| 43 | type plan15ConsoleGetOnly struct{ release.Store } |
| 44 | |
| 45 | func TestPlan15ConsoleStreamingCompositions(t *testing.T) { |
| 46 | for _, stream := range []bool{false, true} { |
| 47 | t.Run(map[bool]string{false: "get-only", true: "streaming"}[stream], func(t *testing.T) { |
| 48 | raw := release.NewDirStore(t.TempDir()) |
| 49 | logical := release.NewPrefixStore(raw, "deployment") |
| 50 | claim := &hostclaim.Claim{V: 2, Host: "x.example", Gen: 1, Origins: &hostclaim.OriginHead{Digest: strings.Repeat("a", 64), Count: 1}} |
| 51 | body, _ := json.Marshal(claim) |
| 52 | key, _ := hostclaim.Key(claim.Host) |
| 53 | tag, err := logical.Put(t.Context(), key, body, "") |
| 54 | if err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | obs := hostclaim.Observed{Host: claim.Host, Claim: claim, ETag: tag} |
| 58 | counter := &plan15ConsoleStream{Store: raw} |
| 59 | var s release.Store = counter |
| 60 | if !stream { |
| 61 | s = plan15ConsoleGetOnly{s} |
| 62 | } |
| 63 | s = release.NewPrefixStore(s, "deployment") |
| 64 | s, err = hostops.NewConditionalRecordStore(s, make([]byte, 32)) |
| 65 | if err != nil { |
| 66 | t.Fatal(err) |
| 67 | } |
| 68 | s, err = hostops.NewMutationBoundary(true, s, func(context.Context, hostops.MutationTarget) error { return nil }) |
| 69 | if err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | s = claimAliasStore{data: claimEffectStore{data: s}} |
| 73 | if got, err := hostclaim.AdmittedOrigin(t.Context(), s, obs, strings.Repeat("b", 64), "instances/x.example", "ie", "real"); got || !errors.Is(err, hostclaim.ErrMalformed) { |
| 74 | t.Fatalf("proof %v %v", got, err) |
| 75 | } |
| 76 | if stream && (counter.read != 4097 || counter.closed != 1) { |
| 77 | t.Fatalf("read=%d closed=%d", counter.read, counter.closed) |
| 78 | } |
| 79 | if !stream && (counter.read != 0 || counter.closed != 0) { |
| 80 | t.Fatal("opened get-only store") |
| 81 | } |
| 82 | }) |
| 83 | } |
| 84 | } |
| 85 | |