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