github.com/google/go-github/v66@v66.0.0/github/copilot_test.go (about)

     1  // Copyright 2023 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"net/http"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  )
    18  
    19  // Test invalid JSON responses, valid responses are covered in the other tests
    20  func TestCopilotSeatDetails_UnmarshalJSON(t *testing.T) {
    21  	t.Parallel()
    22  	tests := []struct {
    23  		name    string
    24  		data    string
    25  		want    *CopilotSeatDetails
    26  		wantErr bool
    27  	}{
    28  		{
    29  			name: "Invalid JSON",
    30  			data: `{`,
    31  			want: &CopilotSeatDetails{
    32  				Assignee: nil,
    33  			},
    34  			wantErr: true,
    35  		},
    36  		{
    37  			name: "Invalid top level type",
    38  			data: `{
    39  					"assignee": {
    40  						"type": "User",
    41  						"name": "octokittens",
    42  						"id": 1
    43  					},
    44  					"assigning_team": "this should be an object"
    45  				}`,
    46  			want:    &CopilotSeatDetails{},
    47  			wantErr: true,
    48  		},
    49  		{
    50  			name: "No Type Field",
    51  			data: `{
    52  					"assignee": {
    53  						"name": "octokittens",
    54  						"id": 1
    55  					}
    56  				}`,
    57  			want:    &CopilotSeatDetails{},
    58  			wantErr: true,
    59  		},
    60  		{
    61  			name: "Invalid Assignee Field Type",
    62  			data: `{
    63  					"assignee": "test"
    64  				}`,
    65  			want:    &CopilotSeatDetails{},
    66  			wantErr: true,
    67  		},
    68  		{
    69  			name: "Invalid Assignee Type",
    70  			data: `{
    71  					"assignee": {
    72  						"name": "octokittens",
    73  						"id": 1,
    74  						"type": []
    75  					}
    76  				}`,
    77  			want:    &CopilotSeatDetails{},
    78  			wantErr: true,
    79  		},
    80  		{
    81  			name: "Invalid User",
    82  			data: `{
    83  					"assignee": {
    84  						"type": "User",
    85  						"id": "bad"
    86  					}
    87  				}`,
    88  			want:    &CopilotSeatDetails{},
    89  			wantErr: true,
    90  		},
    91  		{
    92  			name: "Invalid Team",
    93  			data: `{
    94  					"assignee": {
    95  						"type": "Team",
    96  						"id": "bad"
    97  					}
    98  				}`,
    99  			want:    &CopilotSeatDetails{},
   100  			wantErr: true,
   101  		},
   102  		{
   103  			name: "Invalid Organization",
   104  			data: `{
   105  					"assignee": {
   106  						"type": "Organization",
   107  						"id": "bad"
   108  					}
   109  				}`,
   110  			want:    &CopilotSeatDetails{},
   111  			wantErr: true,
   112  		},
   113  	}
   114  
   115  	for _, tc := range tests {
   116  		tc := tc
   117  		seatDetails := &CopilotSeatDetails{}
   118  
   119  		t.Run(tc.name, func(t *testing.T) {
   120  			t.Parallel()
   121  			err := json.Unmarshal([]byte(tc.data), seatDetails)
   122  			if err == nil && tc.wantErr {
   123  				t.Errorf("CopilotSeatDetails.UnmarshalJSON returned nil instead of an error")
   124  			}
   125  			if err != nil && !tc.wantErr {
   126  				t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   127  			}
   128  			if !cmp.Equal(tc.want, seatDetails) {
   129  				t.Errorf("CopilotSeatDetails.UnmarshalJSON expected %+v, got %+v", tc.want, seatDetails)
   130  			}
   131  		})
   132  	}
   133  }
   134  
   135  func TestCopilotService_GetSeatDetailsUser(t *testing.T) {
   136  	t.Parallel()
   137  	data := `{
   138  				"assignee": {
   139  					"type": "User",
   140  					"id": 1
   141  				}
   142  			}`
   143  
   144  	seatDetails := &CopilotSeatDetails{}
   145  
   146  	err := json.Unmarshal([]byte(data), seatDetails)
   147  	if err != nil {
   148  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   149  	}
   150  
   151  	want := &User{
   152  		ID:   Int64(1),
   153  		Type: String("User"),
   154  	}
   155  
   156  	if got, ok := seatDetails.GetUser(); ok && !cmp.Equal(got, want) {
   157  		t.Errorf("CopilotSeatDetails.GetTeam returned %+v, want %+v", got, want)
   158  	} else if !ok {
   159  		t.Errorf("CopilotSeatDetails.GetUser returned false, expected true")
   160  	}
   161  
   162  	data = `{
   163  				"assignee": {
   164  					"type": "Organization",
   165  					"id": 1
   166  				}
   167  			}`
   168  
   169  	bad := &Organization{
   170  		ID:   Int64(1),
   171  		Type: String("Organization"),
   172  	}
   173  
   174  	err = json.Unmarshal([]byte(data), seatDetails)
   175  	if err != nil {
   176  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   177  	}
   178  
   179  	if got, ok := seatDetails.GetUser(); ok {
   180  		t.Errorf("CopilotSeatDetails.GetUser returned true, expected false. Returned %v, expected %v", got, bad)
   181  	}
   182  }
   183  
   184  func TestCopilotService_GetSeatDetailsTeam(t *testing.T) {
   185  	t.Parallel()
   186  	data := `{
   187  				"assignee": {
   188  					"type": "Team",
   189  					"id": 1
   190  				}
   191  			}`
   192  
   193  	seatDetails := &CopilotSeatDetails{}
   194  
   195  	err := json.Unmarshal([]byte(data), seatDetails)
   196  	if err != nil {
   197  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   198  	}
   199  
   200  	want := &Team{
   201  		ID: Int64(1),
   202  	}
   203  
   204  	if got, ok := seatDetails.GetTeam(); ok && !cmp.Equal(got, want) {
   205  		t.Errorf("CopilotSeatDetails.GetTeam returned %+v, want %+v", got, want)
   206  	} else if !ok {
   207  		t.Errorf("CopilotSeatDetails.GetTeam returned false, expected true")
   208  	}
   209  
   210  	data = `{
   211  				"assignee": {
   212  					"type": "User",
   213  					"id": 1
   214  				}
   215  			}`
   216  
   217  	bad := &User{
   218  		ID:   Int64(1),
   219  		Type: String("User"),
   220  	}
   221  
   222  	err = json.Unmarshal([]byte(data), seatDetails)
   223  	if err != nil {
   224  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   225  	}
   226  
   227  	if got, ok := seatDetails.GetTeam(); ok {
   228  		t.Errorf("CopilotSeatDetails.GetTeam returned true, expected false. Returned %v, expected %v", got, bad)
   229  	}
   230  }
   231  
   232  func TestCopilotService_GetSeatDetailsOrganization(t *testing.T) {
   233  	t.Parallel()
   234  	data := `{
   235  				"assignee": {
   236  					"type": "Organization",
   237  					"id": 1
   238  				}
   239  			}`
   240  
   241  	seatDetails := &CopilotSeatDetails{}
   242  
   243  	err := json.Unmarshal([]byte(data), seatDetails)
   244  	if err != nil {
   245  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   246  	}
   247  
   248  	want := &Organization{
   249  		ID:   Int64(1),
   250  		Type: String("Organization"),
   251  	}
   252  
   253  	if got, ok := seatDetails.GetOrganization(); ok && !cmp.Equal(got, want) {
   254  		t.Errorf("CopilotSeatDetails.GetOrganization returned %+v, want %+v", got, want)
   255  	} else if !ok {
   256  		t.Errorf("CopilotSeatDetails.GetOrganization returned false, expected true")
   257  	}
   258  
   259  	data = `{
   260  				"assignee": {
   261  					"type": "Team",
   262  					"id": 1
   263  				}
   264  			}`
   265  
   266  	bad := &Team{
   267  		ID: Int64(1),
   268  	}
   269  
   270  	err = json.Unmarshal([]byte(data), seatDetails)
   271  	if err != nil {
   272  		t.Errorf("CopilotSeatDetails.UnmarshalJSON returned an unexpected error: %v", err)
   273  	}
   274  
   275  	if got, ok := seatDetails.GetOrganization(); ok {
   276  		t.Errorf("CopilotSeatDetails.GetOrganization returned true, expected false. Returned %v, expected %v", got, bad)
   277  	}
   278  }
   279  
   280  func TestCopilotService_GetCopilotBilling(t *testing.T) {
   281  	t.Parallel()
   282  	client, mux, _ := setup(t)
   283  
   284  	mux.HandleFunc("/orgs/o/copilot/billing", func(w http.ResponseWriter, r *http.Request) {
   285  		testMethod(t, r, "GET")
   286  		fmt.Fprint(w, `{
   287  			"seat_breakdown": {
   288  				"total": 12,
   289  				"added_this_cycle": 9,
   290  				"pending_invitation": 0,
   291  				"pending_cancellation": 0,
   292  				"active_this_cycle": 12,
   293  				"inactive_this_cycle": 11
   294  			},
   295  			"seat_management_setting": "assign_selected",
   296  			"public_code_suggestions": "block"
   297  			}`)
   298  	})
   299  
   300  	ctx := context.Background()
   301  	got, _, err := client.Copilot.GetCopilotBilling(ctx, "o")
   302  	if err != nil {
   303  		t.Errorf("Copilot.GetCopilotBilling returned error: %v", err)
   304  	}
   305  
   306  	want := &CopilotOrganizationDetails{
   307  		SeatBreakdown: &CopilotSeatBreakdown{
   308  			Total:               12,
   309  			AddedThisCycle:      9,
   310  			PendingInvitation:   0,
   311  			PendingCancellation: 0,
   312  			ActiveThisCycle:     12,
   313  			InactiveThisCycle:   11,
   314  		},
   315  		PublicCodeSuggestions: "block",
   316  		CopilotChat:           "",
   317  		SeatManagementSetting: "assign_selected",
   318  	}
   319  	if !cmp.Equal(got, want) {
   320  		t.Errorf("Copilot.GetCopilotBilling returned %+v, want %+v", got, want)
   321  	}
   322  
   323  	const methodName = "GetCopilotBilling"
   324  
   325  	testBadOptions(t, methodName, func() (err error) {
   326  		_, _, err = client.Copilot.GetCopilotBilling(ctx, "\n")
   327  		return err
   328  	})
   329  
   330  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   331  		got, resp, err := client.Copilot.GetCopilotBilling(ctx, "o")
   332  		if got != nil {
   333  			t.Errorf("Copilot.GetCopilotBilling returned %+v, want nil", got)
   334  		}
   335  		return resp, err
   336  	})
   337  }
   338  
   339  func TestCopilotService_ListCopilotSeats(t *testing.T) {
   340  	t.Parallel()
   341  	client, mux, _ := setup(t)
   342  
   343  	mux.HandleFunc("/orgs/o/copilot/billing/seats", func(w http.ResponseWriter, r *http.Request) {
   344  		testMethod(t, r, "GET")
   345  		testFormValues(t, r, values{
   346  			"per_page": "100",
   347  			"page":     "1",
   348  		})
   349  		fmt.Fprint(w, `{
   350  				"total_seats": 4,
   351  				"seats": [
   352  					{
   353  						"created_at": "2021-08-03T18:00:00-06:00",
   354  						"updated_at": "2021-09-23T15:00:00-06:00",
   355  						"pending_cancellation_date": null,
   356  						"last_activity_at": "2021-10-14T00:53:32-06:00",
   357  						"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
   358  						"assignee": {
   359  							"login": "octocat",
   360  							"id": 1,
   361  							"node_id": "MDQ6VXNlcjE=",
   362  							"avatar_url": "https://github.com/images/error/octocat_happy.gif",
   363  							"gravatar_id": "",
   364  							"url": "https://api.github.com/users/octocat",
   365  							"html_url": "https://github.com/octocat",
   366  							"followers_url": "https://api.github.com/users/octocat/followers",
   367  							"following_url": "https://api.github.com/users/octocat/following{/other_user}",
   368  							"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
   369  							"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
   370  							"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
   371  							"organizations_url": "https://api.github.com/users/octocat/orgs",
   372  							"repos_url": "https://api.github.com/users/octocat/repos",
   373  							"events_url": "https://api.github.com/users/octocat/events{/privacy}",
   374  							"received_events_url": "https://api.github.com/users/octocat/received_events",
   375  							"type": "User",
   376  							"site_admin": false
   377  						},
   378  						"assigning_team": {
   379  							"id": 1,
   380  							"node_id": "MDQ6VGVhbTE=",
   381  							"url": "https://api.github.com/teams/1",
   382  							"html_url": "https://github.com/orgs/github/teams/justice-league",
   383  							"name": "Justice League",
   384  							"slug": "justice-league",
   385  							"description": "A great team.",
   386  							"privacy": "closed",
   387  							"notification_setting": "notifications_enabled",
   388  							"permission": "admin",
   389  							"members_url": "https://api.github.com/teams/1/members{/member}",
   390  							"repositories_url": "https://api.github.com/teams/1/repos",
   391  							"parent": null
   392  						}
   393  					},
   394  					{
   395  						"created_at": "2021-09-23T18:00:00-06:00",
   396  						"updated_at": "2021-09-23T15:00:00-06:00",
   397  						"pending_cancellation_date": "2021-11-01",
   398  						"last_activity_at": "2021-10-13T00:53:32-06:00",
   399  						"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
   400  						"assignee": {
   401  							"login": "octokitten",
   402  							"id": 1,
   403  							"node_id": "MDQ76VNlcjE=",
   404  							"avatar_url": "https://github.com/images/error/octokitten_happy.gif",
   405  							"gravatar_id": "",
   406  							"url": "https://api.github.com/users/octokitten",
   407  							"html_url": "https://github.com/octokitten",
   408  							"followers_url": "https://api.github.com/users/octokitten/followers",
   409  							"following_url": "https://api.github.com/users/octokitten/following{/other_user}",
   410  							"gists_url": "https://api.github.com/users/octokitten/gists{/gist_id}",
   411  							"starred_url": "https://api.github.com/users/octokitten/starred{/owner}{/repo}",
   412  							"subscriptions_url": "https://api.github.com/users/octokitten/subscriptions",
   413  							"organizations_url": "https://api.github.com/users/octokitten/orgs",
   414  							"repos_url": "https://api.github.com/users/octokitten/repos",
   415  							"events_url": "https://api.github.com/users/octokitten/events{/privacy}",
   416  							"received_events_url": "https://api.github.com/users/octokitten/received_events",
   417  							"type": "User",
   418  							"site_admin": false
   419  						}
   420  					},
   421  					{
   422  						"created_at": "2021-09-23T18:00:00-06:00",
   423  						"updated_at": "2021-09-23T15:00:00-06:00",
   424  						"pending_cancellation_date": "2021-11-01",
   425  						"last_activity_at": "2021-10-13T00:53:32-06:00",
   426  						"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
   427  						"assignee": {
   428  							"name": "octokittens",
   429  							"id": 1,
   430  							"type": "Team"
   431  						}
   432  					},
   433  					{
   434  						"created_at": "2021-09-23T18:00:00-06:00",
   435  						"updated_at": "2021-09-23T15:00:00-06:00",
   436  						"pending_cancellation_date": "2021-11-01",
   437  						"last_activity_at": "2021-10-13T00:53:32-06:00",
   438  						"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
   439  						"assignee": {
   440  							"name": "octocats",
   441  							"id": 1,
   442  							"type": "Organization"
   443  						}
   444  					}
   445  				]
   446  			}`)
   447  	})
   448  
   449  	tmp, err := time.Parse(time.RFC3339, "2021-08-03T18:00:00-06:00")
   450  	if err != nil {
   451  		panic(err)
   452  	}
   453  	createdAt1 := Timestamp{tmp}
   454  
   455  	tmp, err = time.Parse(time.RFC3339, "2021-09-23T15:00:00-06:00")
   456  	if err != nil {
   457  		panic(err)
   458  	}
   459  	updatedAt1 := Timestamp{tmp}
   460  
   461  	tmp, err = time.Parse(time.RFC3339, "2021-10-14T00:53:32-06:00")
   462  	if err != nil {
   463  		panic(err)
   464  	}
   465  	lastActivityAt1 := Timestamp{tmp}
   466  
   467  	tmp, err = time.Parse(time.RFC3339, "2021-09-23T18:00:00-06:00")
   468  	if err != nil {
   469  		panic(err)
   470  	}
   471  	createdAt2 := Timestamp{tmp}
   472  
   473  	tmp, err = time.Parse(time.RFC3339, "2021-09-23T15:00:00-06:00")
   474  	if err != nil {
   475  		panic(err)
   476  	}
   477  	updatedAt2 := Timestamp{tmp}
   478  
   479  	tmp, err = time.Parse(time.RFC3339, "2021-10-13T00:53:32-06:00")
   480  	if err != nil {
   481  		panic(err)
   482  	}
   483  	lastActivityAt2 := Timestamp{tmp}
   484  
   485  	ctx := context.Background()
   486  	opts := &ListOptions{Page: 1, PerPage: 100}
   487  	got, _, err := client.Copilot.ListCopilotSeats(ctx, "o", opts)
   488  	if err != nil {
   489  		t.Errorf("Copilot.ListCopilotSeats returned error: %v", err)
   490  	}
   491  
   492  	want := &ListCopilotSeatsResponse{
   493  		TotalSeats: 4,
   494  		Seats: []*CopilotSeatDetails{
   495  			{
   496  				Assignee: &User{
   497  					Login:             String("octocat"),
   498  					ID:                Int64(1),
   499  					NodeID:            String("MDQ6VXNlcjE="),
   500  					AvatarURL:         String("https://github.com/images/error/octocat_happy.gif"),
   501  					GravatarID:        String(""),
   502  					URL:               String("https://api.github.com/users/octocat"),
   503  					HTMLURL:           String("https://github.com/octocat"),
   504  					FollowersURL:      String("https://api.github.com/users/octocat/followers"),
   505  					FollowingURL:      String("https://api.github.com/users/octocat/following{/other_user}"),
   506  					GistsURL:          String("https://api.github.com/users/octocat/gists{/gist_id}"),
   507  					StarredURL:        String("https://api.github.com/users/octocat/starred{/owner}{/repo}"),
   508  					SubscriptionsURL:  String("https://api.github.com/users/octocat/subscriptions"),
   509  					OrganizationsURL:  String("https://api.github.com/users/octocat/orgs"),
   510  					ReposURL:          String("https://api.github.com/users/octocat/repos"),
   511  					EventsURL:         String("https://api.github.com/users/octocat/events{/privacy}"),
   512  					ReceivedEventsURL: String("https://api.github.com/users/octocat/received_events"),
   513  					Type:              String("User"),
   514  					SiteAdmin:         Bool(false),
   515  				},
   516  				AssigningTeam: &Team{
   517  					ID:              Int64(1),
   518  					NodeID:          String("MDQ6VGVhbTE="),
   519  					URL:             String("https://api.github.com/teams/1"),
   520  					HTMLURL:         String("https://github.com/orgs/github/teams/justice-league"),
   521  					Name:            String("Justice League"),
   522  					Slug:            String("justice-league"),
   523  					Description:     String("A great team."),
   524  					Privacy:         String("closed"),
   525  					Permission:      String("admin"),
   526  					MembersURL:      String("https://api.github.com/teams/1/members{/member}"),
   527  					RepositoriesURL: String("https://api.github.com/teams/1/repos"),
   528  					Parent:          nil,
   529  				},
   530  				CreatedAt:               &createdAt1,
   531  				UpdatedAt:               &updatedAt1,
   532  				PendingCancellationDate: nil,
   533  				LastActivityAt:          &lastActivityAt1,
   534  				LastActivityEditor:      String("vscode/1.77.3/copilot/1.86.82"),
   535  			},
   536  			{
   537  				Assignee: &User{
   538  					Login:             String("octokitten"),
   539  					ID:                Int64(1),
   540  					NodeID:            String("MDQ76VNlcjE="),
   541  					AvatarURL:         String("https://github.com/images/error/octokitten_happy.gif"),
   542  					GravatarID:        String(""),
   543  					URL:               String("https://api.github.com/users/octokitten"),
   544  					HTMLURL:           String("https://github.com/octokitten"),
   545  					FollowersURL:      String("https://api.github.com/users/octokitten/followers"),
   546  					FollowingURL:      String("https://api.github.com/users/octokitten/following{/other_user}"),
   547  					GistsURL:          String("https://api.github.com/users/octokitten/gists{/gist_id}"),
   548  					StarredURL:        String("https://api.github.com/users/octokitten/starred{/owner}{/repo}"),
   549  					SubscriptionsURL:  String("https://api.github.com/users/octokitten/subscriptions"),
   550  					OrganizationsURL:  String("https://api.github.com/users/octokitten/orgs"),
   551  					ReposURL:          String("https://api.github.com/users/octokitten/repos"),
   552  					EventsURL:         String("https://api.github.com/users/octokitten/events{/privacy}"),
   553  					ReceivedEventsURL: String("https://api.github.com/users/octokitten/received_events"),
   554  					Type:              String("User"),
   555  					SiteAdmin:         Bool(false),
   556  				},
   557  				AssigningTeam:           nil,
   558  				CreatedAt:               &createdAt2,
   559  				UpdatedAt:               &updatedAt2,
   560  				PendingCancellationDate: String("2021-11-01"),
   561  				LastActivityAt:          &lastActivityAt2,
   562  				LastActivityEditor:      String("vscode/1.77.3/copilot/1.86.82"),
   563  			},
   564  			{
   565  				Assignee: &Team{
   566  					ID:   Int64(1),
   567  					Name: String("octokittens"),
   568  				},
   569  				AssigningTeam:           nil,
   570  				CreatedAt:               &createdAt2,
   571  				UpdatedAt:               &updatedAt2,
   572  				PendingCancellationDate: String("2021-11-01"),
   573  				LastActivityAt:          &lastActivityAt2,
   574  				LastActivityEditor:      String("vscode/1.77.3/copilot/1.86.82"),
   575  			},
   576  			{
   577  				Assignee: &Organization{
   578  					ID:   Int64(1),
   579  					Name: String("octocats"),
   580  					Type: String("Organization"),
   581  				},
   582  				AssigningTeam:           nil,
   583  				CreatedAt:               &createdAt2,
   584  				UpdatedAt:               &updatedAt2,
   585  				PendingCancellationDate: String("2021-11-01"),
   586  				LastActivityAt:          &lastActivityAt2,
   587  				LastActivityEditor:      String("vscode/1.77.3/copilot/1.86.82"),
   588  			},
   589  		},
   590  	}
   591  
   592  	if !cmp.Equal(got, want) {
   593  		t.Errorf("Copilot.ListCopilotSeats returned %+v, want %+v", got, want)
   594  	}
   595  
   596  	const methodName = "ListCopilotSeats"
   597  
   598  	testBadOptions(t, methodName, func() (err error) {
   599  		_, _, err = client.Copilot.ListCopilotSeats(ctx, "\n", opts)
   600  		return err
   601  	})
   602  
   603  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   604  		got, resp, err := client.Copilot.ListCopilotSeats(ctx, "o", opts)
   605  		if got != nil {
   606  			t.Errorf("Copilot.ListCopilotSeats returned %+v, want nil", got)
   607  		}
   608  		return resp, err
   609  	})
   610  }
   611  
   612  func TestCopilotService_AddCopilotTeams(t *testing.T) {
   613  	t.Parallel()
   614  	client, mux, _ := setup(t)
   615  
   616  	mux.HandleFunc("/orgs/o/copilot/billing/selected_teams", func(w http.ResponseWriter, r *http.Request) {
   617  		testMethod(t, r, "POST")
   618  		testBody(t, r, `{"selected_teams":["team1","team2"]}`+"\n")
   619  		fmt.Fprint(w, `{"seats_created": 2}`)
   620  	})
   621  
   622  	ctx := context.Background()
   623  	got, _, err := client.Copilot.AddCopilotTeams(ctx, "o", []string{"team1", "team2"})
   624  	if err != nil {
   625  		t.Errorf("Copilot.AddCopilotTeams returned error: %v", err)
   626  	}
   627  
   628  	want := &SeatAssignments{SeatsCreated: 2}
   629  
   630  	if !cmp.Equal(got, want) {
   631  		t.Errorf("Copilot.AddCopilotTeams returned %+v, want %+v", got, want)
   632  	}
   633  
   634  	const methodName = "AddCopilotTeams"
   635  
   636  	testBadOptions(t, methodName, func() (err error) {
   637  		_, _, err = client.Copilot.AddCopilotTeams(ctx, "\n", []string{"team1", "team2"})
   638  		return err
   639  	})
   640  
   641  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   642  		got, resp, err := client.Copilot.AddCopilotTeams(ctx, "o", []string{"team1", "team2"})
   643  		if got != nil {
   644  			t.Errorf("Copilot.AddCopilotTeams returned %+v, want nil", got)
   645  		}
   646  		return resp, err
   647  	})
   648  }
   649  
   650  func TestCopilotService_RemoveCopilotTeams(t *testing.T) {
   651  	t.Parallel()
   652  	client, mux, _ := setup(t)
   653  
   654  	mux.HandleFunc("/orgs/o/copilot/billing/selected_teams", func(w http.ResponseWriter, r *http.Request) {
   655  		testMethod(t, r, "DELETE")
   656  		testBody(t, r, `{"selected_teams":["team1","team2"]}`+"\n")
   657  		fmt.Fprint(w, `{"seats_cancelled": 2}`)
   658  	})
   659  
   660  	ctx := context.Background()
   661  	got, _, err := client.Copilot.RemoveCopilotTeams(ctx, "o", []string{"team1", "team2"})
   662  	if err != nil {
   663  		t.Errorf("Copilot.RemoveCopilotTeams returned error: %v", err)
   664  	}
   665  
   666  	want := &SeatCancellations{SeatsCancelled: 2}
   667  
   668  	if !cmp.Equal(got, want) {
   669  		t.Errorf("Copilot.RemoveCopilotTeams returned %+v, want %+v", got, want)
   670  	}
   671  
   672  	const methodName = "RemoveCopilotTeams"
   673  
   674  	testBadOptions(t, methodName, func() (err error) {
   675  		_, _, err = client.Copilot.RemoveCopilotTeams(ctx, "\n", []string{"team1", "team2"})
   676  		return err
   677  	})
   678  
   679  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   680  		got, resp, err := client.Copilot.RemoveCopilotTeams(ctx, "o", []string{"team1", "team2"})
   681  		if got != nil {
   682  			t.Errorf("Copilot.RemoveCopilotTeams returned %+v, want nil", got)
   683  		}
   684  		return resp, err
   685  	})
   686  }
   687  
   688  func TestCopilotService_AddCopilotUsers(t *testing.T) {
   689  	t.Parallel()
   690  	client, mux, _ := setup(t)
   691  
   692  	mux.HandleFunc("/orgs/o/copilot/billing/selected_users", func(w http.ResponseWriter, r *http.Request) {
   693  		testMethod(t, r, "POST")
   694  		testBody(t, r, `{"selected_usernames":["user1","user2"]}`+"\n")
   695  		fmt.Fprint(w, `{"seats_created": 2}`)
   696  	})
   697  
   698  	ctx := context.Background()
   699  	got, _, err := client.Copilot.AddCopilotUsers(ctx, "o", []string{"user1", "user2"})
   700  	if err != nil {
   701  		t.Errorf("Copilot.AddCopilotUsers returned error: %v", err)
   702  	}
   703  
   704  	want := &SeatAssignments{SeatsCreated: 2}
   705  
   706  	if !cmp.Equal(got, want) {
   707  		t.Errorf("Copilot.AddCopilotUsers returned %+v, want %+v", got, want)
   708  	}
   709  
   710  	const methodName = "AddCopilotUsers"
   711  
   712  	testBadOptions(t, methodName, func() (err error) {
   713  		_, _, err = client.Copilot.AddCopilotUsers(ctx, "\n", []string{"user1", "user2"})
   714  		return err
   715  	})
   716  
   717  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   718  		got, resp, err := client.Copilot.AddCopilotUsers(ctx, "o", []string{"user1", "user2"})
   719  		if got != nil {
   720  			t.Errorf("Copilot.AddCopilotUsers returned %+v, want nil", got)
   721  		}
   722  		return resp, err
   723  	})
   724  }
   725  
   726  func TestCopilotService_RemoveCopilotUsers(t *testing.T) {
   727  	t.Parallel()
   728  	client, mux, _ := setup(t)
   729  
   730  	mux.HandleFunc("/orgs/o/copilot/billing/selected_users", func(w http.ResponseWriter, r *http.Request) {
   731  		testMethod(t, r, "DELETE")
   732  		testBody(t, r, `{"selected_usernames":["user1","user2"]}`+"\n")
   733  		fmt.Fprint(w, `{"seats_cancelled": 2}`)
   734  	})
   735  
   736  	ctx := context.Background()
   737  	got, _, err := client.Copilot.RemoveCopilotUsers(ctx, "o", []string{"user1", "user2"})
   738  	if err != nil {
   739  		t.Errorf("Copilot.RemoveCopilotUsers returned error: %v", err)
   740  	}
   741  
   742  	want := &SeatCancellations{SeatsCancelled: 2}
   743  
   744  	if !cmp.Equal(got, want) {
   745  		t.Errorf("Copilot.RemoveCopilotUsers returned %+v, want %+v", got, want)
   746  	}
   747  
   748  	const methodName = "RemoveCopilotUsers"
   749  
   750  	testBadOptions(t, methodName, func() (err error) {
   751  		_, _, err = client.Copilot.RemoveCopilotUsers(ctx, "\n", []string{"user1", "user2"})
   752  		return err
   753  	})
   754  
   755  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   756  		got, resp, err := client.Copilot.RemoveCopilotUsers(ctx, "o", []string{"user1", "user2"})
   757  		if got != nil {
   758  			t.Errorf("Copilot.RemoveCopilotUsers returned %+v, want nil", got)
   759  		}
   760  		return resp, err
   761  	})
   762  }
   763  
   764  func TestCopilotService_GetSeatDetails(t *testing.T) {
   765  	t.Parallel()
   766  	client, mux, _ := setup(t)
   767  
   768  	mux.HandleFunc("/orgs/o/members/u/copilot", func(w http.ResponseWriter, r *http.Request) {
   769  		testMethod(t, r, "GET")
   770  		fmt.Fprint(w, `{
   771  				"created_at": "2021-08-03T18:00:00-06:00",
   772  				"updated_at": "2021-09-23T15:00:00-06:00",
   773  				"pending_cancellation_date": null,
   774  				"last_activity_at": "2021-10-14T00:53:32-06:00",
   775  				"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
   776  				"assignee": {
   777  					"login": "octocat",
   778  					"id": 1,
   779  					"node_id": "MDQ6VXNlcjE=",
   780  					"avatar_url": "https://github.com/images/error/octocat_happy.gif",
   781  					"gravatar_id": "",
   782  					"url": "https://api.github.com/users/octocat",
   783  					"html_url": "https://github.com/octocat",
   784  					"followers_url": "https://api.github.com/users/octocat/followers",
   785  					"following_url": "https://api.github.com/users/octocat/following{/other_user}",
   786  					"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
   787  					"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
   788  					"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
   789  					"organizations_url": "https://api.github.com/users/octocat/orgs",
   790  					"repos_url": "https://api.github.com/users/octocat/repos",
   791  					"events_url": "https://api.github.com/users/octocat/events{/privacy}",
   792  					"received_events_url": "https://api.github.com/users/octocat/received_events",
   793  					"type": "User",
   794  					"site_admin": false
   795  				},
   796  				"assigning_team": {
   797  					"id": 1,
   798  					"node_id": "MDQ6VGVhbTE=",
   799  					"url": "https://api.github.com/teams/1",
   800  					"html_url": "https://github.com/orgs/github/teams/justice-league",
   801  					"name": "Justice League",
   802  					"slug": "justice-league",
   803  					"description": "A great team.",
   804  					"privacy": "closed",
   805  					"notification_setting": "notifications_enabled",
   806  					"permission": "admin",
   807  					"members_url": "https://api.github.com/teams/1/members{/member}",
   808  					"repositories_url": "https://api.github.com/teams/1/repos",
   809  					"parent": null
   810  				}
   811  			}`)
   812  	})
   813  
   814  	tmp, err := time.Parse(time.RFC3339, "2021-08-03T18:00:00-06:00")
   815  	if err != nil {
   816  		panic(err)
   817  	}
   818  	createdAt := Timestamp{tmp}
   819  
   820  	tmp, err = time.Parse(time.RFC3339, "2021-09-23T15:00:00-06:00")
   821  	if err != nil {
   822  		panic(err)
   823  	}
   824  	updatedAt := Timestamp{tmp}
   825  
   826  	tmp, err = time.Parse(time.RFC3339, "2021-10-14T00:53:32-06:00")
   827  	if err != nil {
   828  		panic(err)
   829  	}
   830  	lastActivityAt := Timestamp{tmp}
   831  
   832  	ctx := context.Background()
   833  	got, _, err := client.Copilot.GetSeatDetails(ctx, "o", "u")
   834  	if err != nil {
   835  		t.Errorf("Copilot.GetSeatDetails returned error: %v", err)
   836  	}
   837  
   838  	want := &CopilotSeatDetails{
   839  		Assignee: &User{
   840  			Login:             String("octocat"),
   841  			ID:                Int64(1),
   842  			NodeID:            String("MDQ6VXNlcjE="),
   843  			AvatarURL:         String("https://github.com/images/error/octocat_happy.gif"),
   844  			GravatarID:        String(""),
   845  			URL:               String("https://api.github.com/users/octocat"),
   846  			HTMLURL:           String("https://github.com/octocat"),
   847  			FollowersURL:      String("https://api.github.com/users/octocat/followers"),
   848  			FollowingURL:      String("https://api.github.com/users/octocat/following{/other_user}"),
   849  			GistsURL:          String("https://api.github.com/users/octocat/gists{/gist_id}"),
   850  			StarredURL:        String("https://api.github.com/users/octocat/starred{/owner}{/repo}"),
   851  			SubscriptionsURL:  String("https://api.github.com/users/octocat/subscriptions"),
   852  			OrganizationsURL:  String("https://api.github.com/users/octocat/orgs"),
   853  			ReposURL:          String("https://api.github.com/users/octocat/repos"),
   854  			EventsURL:         String("https://api.github.com/users/octocat/events{/privacy}"),
   855  			ReceivedEventsURL: String("https://api.github.com/users/octocat/received_events"),
   856  			Type:              String("User"),
   857  			SiteAdmin:         Bool(false),
   858  		},
   859  		AssigningTeam: &Team{
   860  			ID:              Int64(1),
   861  			NodeID:          String("MDQ6VGVhbTE="),
   862  			URL:             String("https://api.github.com/teams/1"),
   863  			HTMLURL:         String("https://github.com/orgs/github/teams/justice-league"),
   864  			Name:            String("Justice League"),
   865  			Slug:            String("justice-league"),
   866  			Description:     String("A great team."),
   867  			Privacy:         String("closed"),
   868  			Permission:      String("admin"),
   869  			MembersURL:      String("https://api.github.com/teams/1/members{/member}"),
   870  			RepositoriesURL: String("https://api.github.com/teams/1/repos"),
   871  			Parent:          nil,
   872  		},
   873  		CreatedAt:               &createdAt,
   874  		UpdatedAt:               &updatedAt,
   875  		PendingCancellationDate: nil,
   876  		LastActivityAt:          &lastActivityAt,
   877  		LastActivityEditor:      String("vscode/1.77.3/copilot/1.86.82"),
   878  	}
   879  
   880  	if !cmp.Equal(got, want) {
   881  		t.Errorf("Copilot.GetSeatDetails returned %+v, want %+v", got, want)
   882  	}
   883  
   884  	const methodName = "GetSeatDetails"
   885  
   886  	testBadOptions(t, methodName, func() (err error) {
   887  		_, _, err = client.Copilot.GetSeatDetails(ctx, "\n", "u")
   888  		return err
   889  	})
   890  
   891  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   892  		got, resp, err := client.Copilot.GetSeatDetails(ctx, "o", "u")
   893  		if got != nil {
   894  			t.Errorf("Copilot.GetSeatDetails returned %+v, want nil", got)
   895  		}
   896  		return resp, err
   897  	})
   898  }