blob: 704de364324368de1ff69d7009fe42ba4dd8ece0 [file] [log] [blame]
Serge Bazanskibee272f2022-09-13 13:52:42 +02001package server
2
3import (
4 "bytes"
5 "context"
6 "encoding/binary"
7 "fmt"
8 "net/http"
9 "strconv"
10 "testing"
11
12 "google.golang.org/grpc/codes"
13 "google.golang.org/protobuf/proto"
14
15 apb "source.monogon.dev/cloud/api"
Serge Bazanskia5baa872022-09-15 18:49:35 +020016 "source.monogon.dev/cloud/apigw/model"
Serge Bazanskibee272f2022-09-13 13:52:42 +020017 "source.monogon.dev/cloud/lib/component"
18)
19
Serge Bazanskia5baa872022-09-15 18:49:35 +020020func dut() *Server {
21 return &Server{
Serge Bazanskibee272f2022-09-13 13:52:42 +020022 Config: Config{
Serge Bazanskia5baa872022-09-15 18:49:35 +020023 Component: component.ComponentConfig{
Serge Bazanskibee272f2022-09-13 13:52:42 +020024 GRPCListenAddress: ":0",
25 DevCerts: true,
26 DevCertsPath: "/tmp/foo",
27 },
Serge Bazanskia5baa872022-09-15 18:49:35 +020028 Database: component.CockroachConfig{
29 InMemory: true,
30 },
Serge Bazanskibee272f2022-09-13 13:52:42 +020031 PublicListenAddress: ":0",
32 },
33 }
Serge Bazanskia5baa872022-09-15 18:49:35 +020034}
Serge Bazanskibee272f2022-09-13 13:52:42 +020035
Serge Bazanskia5baa872022-09-15 18:49:35 +020036// TestPublicSimple ensures the public grpc-web listener is working.
37func TestPublicSimple(t *testing.T) {
38 s := dut()
Serge Bazanskibee272f2022-09-13 13:52:42 +020039 ctx := context.Background()
40 s.Start(ctx)
41
42 // Craft a gRPC-Web request from scratch. There doesn't seem to be a
43 // well-supported library to do this.
44
45 // The request is \0 ++ uint32be(len(req)) ++ req.
46 msgBytes, err := proto.Marshal(&apb.WhoAmIRequest{})
47 if err != nil {
48 t.Fatalf("Could not marshal request body: %v", err)
49 }
50 buf := bytes.NewBuffer(nil)
51 binary.Write(buf, binary.BigEndian, byte(0))
52 binary.Write(buf, binary.BigEndian, uint32(len(msgBytes)))
53 buf.Write(msgBytes)
54
55 // Perform the request. Set minimum headers required for gRPC-Web to recognize
56 // this as a gRPC-Web request.
57 req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/cloud.api.IAM/WhoAmI", s.ListenPublic), buf)
58 if err != nil {
59 t.Fatalf("Could not create request: %v", err)
60 }
61 req.Header.Set("Content-Type", "application/grpc-web+proto")
62 req.Header.Set("X-Grpc-Web", "1")
63
64 res, err := http.DefaultClient.Do(req)
65 if err != nil {
66 t.Fatalf("Could not perform request: %v", err)
67 }
68 // Regardless for RPC status, 200 should always be returned.
69 if want, got := 200, res.StatusCode; want != got {
70 t.Errorf("Wanted code %d, got %d", want, got)
71 }
72
73 // Expect endpoint to return 'unimplemented'.
74 code, _ := strconv.Atoi(res.Header.Get("Grpc-Status"))
75 if want, got := uint32(codes.Unimplemented), uint32(code); want != got {
76 t.Errorf("Wanted code %d, got %d", want, got)
77 }
78 if want, got := "unimplemented", res.Header.Get("Grpc-Message"); want != got {
79 t.Errorf("Wanted message %q, got %q", want, got)
80 }
81}
Serge Bazanskia5baa872022-09-15 18:49:35 +020082
83// TestUserSimple makes sure we can add and retrieve users. This is a low-level
84// test which mostly exercises the machinery to bring up a working database in
85// tests.
86func TestUserSimple(t *testing.T) {
87 s := dut()
88 ctx := context.Background()
89 s.Start(ctx)
90
91 db, err := s.Config.Database.Connect()
92 if err != nil {
93 t.Fatalf("Connecting to the database failed: %v", err)
94 }
95 q := model.New(db)
96
97 // Start out with no account by sub 'test'.
98 accounts, err := q.GetAccountByOIDC(ctx, "test")
99 if err != nil {
100 t.Fatalf("Retrieving accounts failed: %v", err)
101 }
102 if want, got := 0, len(accounts); want != got {
103 t.Fatalf("Expected no accounts at first, got %d", got)
104 }
105
106 // Create a new test account for sub 'test'.
107 _, err = q.InitializeAccountFromOIDC(ctx, model.InitializeAccountFromOIDCParams{
108 AccountOidcSub: "test",
109 AccountDisplayName: "Test User",
110 })
111 if err != nil {
112 t.Fatalf("Creating new account failed: %v", err)
113 }
114
115 // Expect this account to be available now.
116 accounts, err = q.GetAccountByOIDC(ctx, "test")
117 if err != nil {
118 t.Fatalf("Retrieving accounts failed: %v", err)
119 }
120 if want, got := 1, len(accounts); want != got {
121 t.Fatalf("Expected exactly one account after creation, got %d", got)
122 }
123 if want, got := "Test User", accounts[0].AccountDisplayName; want != got {
124 t.Fatalf("Expected to read back display name %q, got %q", want, got)
125 }
126}