github.com/navikt/knorten@v0.0.0-20240419132333-1333f46ed8b6/pkg/api/team_test.go (about) 1 package api 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/navikt/knorten/pkg/chart" 14 "github.com/navikt/knorten/pkg/database" 15 "github.com/navikt/knorten/pkg/database/gensql" 16 ) 17 18 func TestTeamAPI(t *testing.T) { 19 ctx := context.Background() 20 21 newTeam := "new-team" 22 existingTeam := "existing-team" 23 existingTeamID := existingTeam + "-1234" 24 err := repo.TeamCreate(ctx, gensql.Team{ 25 ID: existingTeamID, 26 Slug: existingTeam, 27 Users: []string{testUser.Email}, 28 }) 29 if err != nil { 30 t.Fatal(err) 31 } 32 33 t.Cleanup(func() { 34 if err := repo.TeamDelete(ctx, existingTeamID); err != nil { 35 t.Errorf("cleaning up after team tests: %v", err) 36 } 37 }) 38 39 t.Run("get new team html", func(t *testing.T) { 40 resp, err := server.Client().Get(fmt.Sprintf("%v/team/new", server.URL)) 41 if err != nil { 42 t.Error(err) 43 } 44 defer resp.Body.Close() 45 46 if resp.StatusCode != http.StatusOK { 47 t.Errorf("Status code is %v, should be %v", resp.StatusCode, http.StatusOK) 48 } 49 50 if resp.Header.Get("Content-Type") != htmlContentType { 51 t.Errorf("Content-Type header is %v, should be %v", resp.Header.Get("Content-Type"), htmlContentType) 52 } 53 54 received, err := io.ReadAll(resp.Body) 55 if err != nil { 56 t.Error(err) 57 } 58 59 receivedMinimized, err := minimizeHTML(string(received)) 60 if err != nil { 61 t.Error(err) 62 } 63 64 expected, err := createExpectedHTML("team/new", map[string]any{ 65 "form": gensql.Team{ 66 Users: []string{testUser.Email}, 67 }, 68 }) 69 if err != nil { 70 t.Error(err) 71 } 72 expectedMinimized, err := minimizeHTML(expected) 73 if err != nil { 74 t.Error(err) 75 } 76 77 if diff := cmp.Diff(expectedMinimized, receivedMinimized); diff != "" { 78 t.Errorf("mismatch (-want +got):\n%s", diff) 79 } 80 }) 81 82 t.Run("create team", func(t *testing.T) { 83 data := url.Values{"team": {newTeam}, "users[]": []string{testUser.Email}} 84 resp, err := server.Client().PostForm(fmt.Sprintf("%v/team/new", server.URL), data) 85 if err != nil { 86 t.Error(err) 87 } 88 resp.Body.Close() 89 90 if resp.StatusCode != http.StatusOK { 91 t.Errorf("create team: expected status code 200, got %v", resp.StatusCode) 92 } 93 94 events, err := repo.EventsGetType(ctx, database.EventTypeCreateTeam) 95 if err != nil { 96 t.Error(err) 97 } 98 99 eventPayload, err := getEventForTeam(events, newTeam) 100 if err != nil { 101 t.Error(err) 102 } 103 104 if eventPayload.ID == "" { 105 t.Errorf("create team: no event registered for team %v", newTeam) 106 } 107 108 if eventPayload.Slug != newTeam { 109 t.Errorf("create team: expected slug %v, got %v", newTeam, eventPayload.Slug) 110 } 111 112 if len(eventPayload.Users) != 1 { 113 t.Errorf("create team: expected 1 number of users, got %v", len(eventPayload.Users)) 114 } 115 }) 116 117 t.Run("create team - team already exists", func(t *testing.T) { 118 // Disable automatic redirect. For the test we need to add the session cookie to the subsequent GET request manually 119 server.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error { 120 return http.ErrUseLastResponse 121 } 122 existing := gensql.Team{ 123 Slug: "existing-team", 124 ID: "exists-team-1234", 125 Users: []string{testUser.Email}, 126 } 127 if err := repo.TeamCreate(ctx, existing); err != nil { 128 t.Error(err) 129 } 130 t.Cleanup(func() { 131 server.Client().CheckRedirect = nil 132 if err := repo.TeamDelete(ctx, existing.ID); err != nil { 133 t.Error(err) 134 } 135 }) 136 137 data := url.Values{"team": {existing.Slug}, "users[]": []string{testUser.Email}} 138 resp, err := server.Client().PostForm(fmt.Sprintf("%v/team/new", server.URL), data) 139 if err != nil { 140 t.Error(err) 141 } 142 resp.Body.Close() 143 144 if resp.StatusCode != http.StatusSeeOther { 145 t.Errorf("create team: expected status code 303, got %v", resp.StatusCode) 146 } 147 sessionCookie, err := getSessionCookieFromResponse(resp) 148 if err != nil { 149 t.Error(err) 150 } 151 152 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v/team/new", server.URL), nil) 153 if err != nil { 154 t.Error(err) 155 } 156 req.AddCookie(sessionCookie) 157 resp, err = server.Client().Do(req) 158 if err != nil { 159 t.Error(err) 160 } 161 defer resp.Body.Close() 162 163 received, err := io.ReadAll(resp.Body) 164 if err != nil { 165 t.Error(err) 166 } 167 168 receivedMinimized, err := minimizeHTML(string(received)) 169 if err != nil { 170 t.Error(err) 171 } 172 173 expected, err := createExpectedHTML("team/new", map[string]any{ 174 "form": map[string]any{ 175 "Users": []string{testUser.Email}, 176 }, 177 "errors": []string{fmt.Sprintf("team %v already exists", existing.Slug)}, 178 }) 179 if err != nil { 180 t.Error(err) 181 } 182 183 expectedMinimized, err := minimizeHTML(expected) 184 if err != nil { 185 t.Error(err) 186 } 187 188 if diff := cmp.Diff(expectedMinimized, receivedMinimized); diff != "" { 189 t.Errorf("mismatch (-want +got):\n%s", diff) 190 } 191 }) 192 193 t.Run("get edit team - team does not exist", func(t *testing.T) { 194 resp, err := server.Client().Get(fmt.Sprintf("%v/team/%v/edit", server.URL, "noexist")) 195 if err != nil { 196 t.Error(err) 197 } 198 defer resp.Body.Close() 199 200 if resp.StatusCode != http.StatusNotFound { 201 t.Errorf("Status code is %v, should be %v", resp.StatusCode, http.StatusNotFound) 202 } 203 204 if resp.Header.Get("Content-Type") != jsonContentType { 205 t.Errorf("Content-Type header is %v, should be %v", resp.Header.Get("Content-Type"), htmlContentType) 206 } 207 }) 208 209 t.Run("get edit team", func(t *testing.T) { 210 resp, err := server.Client().Get(fmt.Sprintf("%v/team/%v/edit", server.URL, existingTeam)) 211 if err != nil { 212 t.Error(err) 213 } 214 defer resp.Body.Close() 215 216 if resp.StatusCode != http.StatusOK { 217 t.Errorf("Status code is %v, should be %v", resp.StatusCode, http.StatusOK) 218 } 219 220 if resp.Header.Get("Content-Type") != htmlContentType { 221 t.Errorf("Content-Type header is %v, should be %v", resp.Header.Get("Content-Type"), htmlContentType) 222 } 223 224 received, err := io.ReadAll(resp.Body) 225 if err != nil { 226 t.Error(err) 227 } 228 229 receivedMinimized, err := minimizeHTML(string(received)) 230 if err != nil { 231 t.Error(err) 232 } 233 234 expected, err := createExpectedHTML("team/edit", map[string]any{ 235 "team": gensql.TeamGetRow{ 236 ID: existingTeamID, 237 Slug: existingTeam, 238 Users: []string{testUser.Email}, 239 }, 240 }) 241 if err != nil { 242 t.Error(err) 243 } 244 245 expectedMinimized, err := minimizeHTML(expected) 246 if err != nil { 247 t.Error(err) 248 } 249 250 if diff := cmp.Diff(expectedMinimized, receivedMinimized); diff != "" { 251 t.Errorf("mismatch (-want +got):\n%s", diff) 252 } 253 }) 254 255 t.Run("edit team", func(t *testing.T) { 256 users := []string{"user@nav.no"} 257 data := url.Values{"team": {existingTeam}, "owner": {testUser.Email}, "users[]": users, "enableallowlist": {"on"}} 258 resp, err := server.Client().PostForm(fmt.Sprintf("%v/team/%v/edit", server.URL, existingTeam), data) 259 if err != nil { 260 t.Error(err) 261 } 262 resp.Body.Close() 263 264 if resp.StatusCode != http.StatusOK { 265 t.Errorf("edit team: expected status code 200, got %v", resp.StatusCode) 266 } 267 268 events, err := repo.EventsGetType(ctx, database.EventTypeUpdateTeam) 269 if err != nil { 270 t.Error(err) 271 } 272 273 eventPayload, err := getEventForTeam(events, existingTeam) 274 if err != nil { 275 t.Error(err) 276 } 277 278 if eventPayload.ID == "" { 279 t.Errorf("edit team: no event registered for team %v", existingTeam) 280 } 281 282 if eventPayload.Slug != existingTeam { 283 t.Errorf("edit team: expected slug %v, got %v", existingTeam, eventPayload.Slug) 284 } 285 286 if eventPayload.ID != existingTeamID { 287 t.Errorf("edit team: expected team id %v, got %v", existingTeamID, eventPayload.ID) 288 } 289 290 if diff := cmp.Diff(eventPayload.Users, users); diff != "" { 291 t.Errorf("mismatch (-want +got):\n%s", diff) 292 } 293 }) 294 295 t.Run("delete team", func(t *testing.T) { 296 resp, err := server.Client().PostForm(fmt.Sprintf("%v/team/%v/delete", server.URL, existingTeam), nil) 297 if err != nil { 298 t.Error(err) 299 } 300 resp.Body.Close() 301 302 if resp.StatusCode != http.StatusOK { 303 t.Errorf("delete team: expected status code 200, got %v", resp.StatusCode) 304 } 305 306 events, err := repo.EventsGetType(ctx, database.EventTypeDeleteTeam) 307 if err != nil { 308 t.Error(err) 309 } 310 311 if !deleteEventCreatedForTeam(events, existingTeamID) { 312 t.Errorf("delete team: no event registered for team %v", existingTeam) 313 } 314 }) 315 316 t.Run("get team events", func(t *testing.T) { 317 team, err := prepareTeamEventsTest(ctx) 318 if err != nil { 319 t.Errorf("preparing team events test: %v", err) 320 } 321 t.Cleanup(func() { 322 if err := repo.TeamDelete(ctx, team.ID); err != nil { 323 t.Errorf("deleting team in cleanup: %v", err) 324 } 325 }) 326 327 resp, err := server.Client().Get(fmt.Sprintf("%v/team/%v/events", server.URL, team.Slug)) 328 if err != nil { 329 t.Error(err) 330 } 331 defer resp.Body.Close() 332 333 if resp.StatusCode != http.StatusOK { 334 t.Errorf("Status code is %v, should be %v", resp.StatusCode, http.StatusOK) 335 } 336 337 if resp.Header.Get("Content-Type") != htmlContentType { 338 t.Errorf("Content-Type header is %v, should be %v", resp.Header.Get("Content-Type"), htmlContentType) 339 } 340 341 received, err := io.ReadAll(resp.Body) 342 if err != nil { 343 t.Error(err) 344 } 345 346 receivedMinimized, err := minimizeHTML(string(received)) 347 if err != nil { 348 t.Error(err) 349 } 350 351 events, err := repo.EventLogsForOwnerGet(ctx, team.ID, -1) 352 if err != nil { 353 t.Error(err) 354 } 355 356 if len(events) == 0 { 357 t.Errorf("no events stored for team %v", team.ID) 358 } 359 360 expected, err := createExpectedHTML("team/events", map[string]any{ 361 "events": events, 362 "slug": team.Slug, 363 }) 364 if err != nil { 365 t.Error(err) 366 } 367 368 expectedMinimized, err := minimizeHTML(expected) 369 if err != nil { 370 t.Error(err) 371 } 372 373 if diff := cmp.Diff(expectedMinimized, receivedMinimized); diff != "" { 374 t.Errorf("mismatch (-want +got):\n%s", diff) 375 } 376 }) 377 } 378 379 func getEventForTeam(events []gensql.Event, team string) (gensql.Team, error) { 380 for _, event := range events { 381 payload := gensql.Team{} 382 err := json.Unmarshal(event.Payload, &payload) 383 if err != nil { 384 return gensql.Team{}, err 385 } 386 387 if payload.Slug == team { 388 return payload, nil 389 } 390 } 391 392 return gensql.Team{}, nil 393 } 394 395 func deleteEventCreatedForTeam(events []gensql.Event, team string) bool { 396 for _, event := range events { 397 if event.Owner == team { 398 return true 399 } 400 } 401 402 return false 403 } 404 405 func prepareTeamEventsTest(ctx context.Context) (gensql.Team, error) { 406 team := gensql.Team{ 407 ID: "eventtest-team-1234", 408 Slug: "eventtest-team", 409 Users: []string{testUser.Email}, 410 } 411 412 if err := repo.TeamCreate(ctx, team); err != nil { 413 return gensql.Team{}, err 414 } 415 416 // create events 417 if err := repo.RegisterCreateJupyterEvent(ctx, team.ID, chart.JupyterConfigurableValues{}); err != nil { 418 return gensql.Team{}, err 419 } 420 if err := repo.RegisterUpdateJupyterEvent(ctx, team.ID, chart.JupyterConfigurableValues{}); err != nil { 421 return gensql.Team{}, err 422 } 423 if err := repo.RegisterDeleteJupyterEvent(ctx, team.ID); err != nil { 424 return gensql.Team{}, err 425 } 426 427 if err := repo.RegisterCreateAirflowEvent(ctx, team.ID, chart.AirflowConfigurableValues{}); err != nil { 428 return gensql.Team{}, err 429 } 430 431 return team, nil 432 }