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  }