Skip to content

Commit 3ca13b0

Browse files
authored
feat: Add enterprise Visual Studio subscription licensing APIs (#4483)
1 parent 34cec4f commit 3ca13b0

6 files changed

Lines changed: 488 additions & 0 deletions

github/enterprise_licenses.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ type LastLicenseSyncProperties struct {
8989
Error string `json:"error"`
9090
}
9191

92+
// VisualStudioSubscriptionAssignment represents a user's Visual Studio subscription assignment.
93+
type VisualStudioSubscriptionAssignment struct {
94+
VisualStudioSubscriptionEmail *string `json:"visual_studio_subscription_email,omitempty"`
95+
SubscriptionID *string `json:"subscription_id,omitempty"`
96+
Username *string `json:"username,omitempty"`
97+
ManualMatch *bool `json:"manual_match,omitempty"`
98+
}
99+
100+
// VisualStudioSubscriptions represents a list of Visual Studio subscriptions for an enterprise.
101+
type VisualStudioSubscriptions struct {
102+
TotalCount *int `json:"total_count,omitempty"`
103+
VisualStudioSubscriptions []*VisualStudioSubscriptionAssignment `json:"visual_studio_subscriptions,omitempty"`
104+
}
105+
106+
// VisualStudioSubscriptionAssignmentRequest represents the request body to add or update a subscription assignment.
107+
type VisualStudioSubscriptionAssignmentRequest struct {
108+
UserIdentifier *string `json:"user_identifier,omitempty"`
109+
}
110+
111+
// ListVisualStudioSubscriptionsOptions specifies the optional parameters to
112+
// EnterpriseService.ListVisualStudioSubscriptions.
113+
type ListVisualStudioSubscriptionsOptions struct {
114+
ListOptions
115+
116+
// IsUnmatchedOnly filters results to return only unmatched subscriptions.
117+
IsUnmatchedOnly bool `url:"is_unmatched_only,omitempty"`
118+
}
119+
92120
// ListConsumedLicenses collect information about the number of consumed licenses and a collection with all the users with consumed enterprise licenses.
93121
//
94122
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/licensing?apiVersion=2022-11-28#list-enterprise-consumed-licenses
@@ -136,3 +164,67 @@ func (s *EnterpriseService) GetLicenseSyncStatus(ctx context.Context, enterprise
136164

137165
return syncStatus, resp, nil
138166
}
167+
168+
// ListVisualStudioSubscriptions gets a list of Visual Studio subscriptions for an enterprise.
169+
//
170+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/licensing?apiVersion=2022-11-28#get-a-list-of-visual-studio-subscriptions-for-the-enterprise
171+
//
172+
//meta:operation GET /enterprises/{enterprise}/visual-studio-subscriptions
173+
func (s *EnterpriseService) ListVisualStudioSubscriptions(ctx context.Context, enterprise string, opts *ListVisualStudioSubscriptionsOptions) (*VisualStudioSubscriptions, *Response, error) {
174+
u := fmt.Sprintf("enterprises/%v/visual-studio-subscriptions", enterprise)
175+
u, err := addOptions(u, opts)
176+
if err != nil {
177+
return nil, nil, err
178+
}
179+
180+
req, err := s.client.NewRequest(ctx, "GET", u, nil)
181+
if err != nil {
182+
return nil, nil, err
183+
}
184+
185+
var subscriptions *VisualStudioSubscriptions
186+
resp, err := s.client.Do(req, &subscriptions)
187+
if err != nil {
188+
return nil, resp, err
189+
}
190+
191+
return subscriptions, resp, nil
192+
}
193+
194+
// AddOrUpdateVisualStudioSubscriptionAssignment adds or updates a manual match between a user and a Visual Studio subscription.
195+
//
196+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/licensing?apiVersion=2022-11-28#add-or-update-a-visual-studio-subscription-user-match
197+
//
198+
//meta:operation PUT /enterprises/{enterprise}/visual-studio-subscriptions/{visual_studio_subscription_id}
199+
func (s *EnterpriseService) AddOrUpdateVisualStudioSubscriptionAssignment(ctx context.Context, enterprise, subscriptionID string, body VisualStudioSubscriptionAssignmentRequest) (*VisualStudioSubscriptionAssignment, *Response, error) {
200+
u := fmt.Sprintf("enterprises/%v/visual-studio-subscriptions/%v", enterprise, subscriptionID)
201+
202+
req, err := s.client.NewRequest(ctx, "PUT", u, body)
203+
if err != nil {
204+
return nil, nil, err
205+
}
206+
207+
var assignment *VisualStudioSubscriptionAssignment
208+
resp, err := s.client.Do(req, &assignment)
209+
if err != nil {
210+
return nil, resp, err
211+
}
212+
213+
return assignment, resp, nil
214+
}
215+
216+
// DeleteVisualStudioSubscriptionAssignment deletes a manual match between a user and a Visual Studio subscription.
217+
//
218+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/licensing?apiVersion=2022-11-28#delete-a-visual-studio-subscription-user-match
219+
//
220+
//meta:operation DELETE /enterprises/{enterprise}/visual-studio-subscriptions/{visual_studio_subscription_id}
221+
func (s *EnterpriseService) DeleteVisualStudioSubscriptionAssignment(ctx context.Context, enterprise, subscriptionID string) (*Response, error) {
222+
u := fmt.Sprintf("enterprises/%v/visual-studio-subscriptions/%v", enterprise, subscriptionID)
223+
224+
req, err := s.client.NewRequest(ctx, "DELETE", u, nil)
225+
if err != nil {
226+
return nil, err
227+
}
228+
229+
return s.client.Do(req, nil)
230+
}

github/enterprise_licenses_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,143 @@ func TestEnterpriseService_GetLicenseSyncStatus(t *testing.T) {
180180
return resp, err
181181
})
182182
}
183+
184+
func TestEnterpriseService_ListVisualStudioSubscriptions(t *testing.T) {
185+
t.Parallel()
186+
client, mux, _ := setup(t)
187+
188+
mux.HandleFunc("/enterprises/e/visual-studio-subscriptions", func(w http.ResponseWriter, r *http.Request) {
189+
testMethod(t, r, "GET")
190+
testFormValues(t, r, values{"page": "1", "per_page": "10", "is_unmatched_only": "true"})
191+
fmt.Fprint(w, `{
192+
"total_count": 1,
193+
"visual_studio_subscriptions": [{
194+
"visual_studio_subscription_email": "user@example.com",
195+
"subscription_id": "sub-123",
196+
"username": "monalisa",
197+
"manual_match": true
198+
}]
199+
}`)
200+
})
201+
202+
opt := &ListVisualStudioSubscriptionsOptions{
203+
ListOptions: ListOptions{Page: 1, PerPage: 10},
204+
IsUnmatchedOnly: true,
205+
}
206+
ctx := t.Context()
207+
subscriptions, _, err := client.Enterprise.ListVisualStudioSubscriptions(ctx, "e", opt)
208+
if err != nil {
209+
t.Errorf("Enterprise.ListVisualStudioSubscriptions returned error: %v", err)
210+
}
211+
212+
want := &VisualStudioSubscriptions{
213+
TotalCount: Ptr(1),
214+
VisualStudioSubscriptions: []*VisualStudioSubscriptionAssignment{
215+
{
216+
VisualStudioSubscriptionEmail: Ptr("user@example.com"),
217+
SubscriptionID: Ptr("sub-123"),
218+
Username: Ptr("monalisa"),
219+
ManualMatch: Ptr(true),
220+
},
221+
},
222+
}
223+
224+
if !cmp.Equal(subscriptions, want) {
225+
t.Errorf("Enterprise.ListVisualStudioSubscriptions returned %+v, want %+v", subscriptions, want)
226+
}
227+
228+
const methodName = "ListVisualStudioSubscriptions"
229+
testBadOptions(t, methodName, func() (err error) {
230+
_, _, err = client.Enterprise.ListVisualStudioSubscriptions(ctx, "\n", opt)
231+
return err
232+
})
233+
234+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
235+
got, resp, err := client.Enterprise.ListVisualStudioSubscriptions(ctx, "e", opt)
236+
if got != nil {
237+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
238+
}
239+
return resp, err
240+
})
241+
}
242+
243+
func TestEnterpriseService_AddOrUpdateVisualStudioSubscriptionAssignment(t *testing.T) {
244+
t.Parallel()
245+
client, mux, _ := setup(t)
246+
247+
input := VisualStudioSubscriptionAssignmentRequest{
248+
UserIdentifier: Ptr("monalisa"),
249+
}
250+
251+
mux.HandleFunc("/enterprises/e/visual-studio-subscriptions/sub-123", func(w http.ResponseWriter, r *http.Request) {
252+
testMethod(t, r, "PUT")
253+
testJSONBody(t, r, input)
254+
fmt.Fprint(w, `{
255+
"visual_studio_subscription_email": "user@example.com",
256+
"subscription_id": "sub-123",
257+
"username": "monalisa",
258+
"manual_match": true
259+
}`)
260+
})
261+
262+
ctx := t.Context()
263+
assignment, _, err := client.Enterprise.AddOrUpdateVisualStudioSubscriptionAssignment(ctx, "e", "sub-123", input)
264+
if err != nil {
265+
t.Errorf("Enterprise.AddOrUpdateVisualStudioSubscriptionAssignment returned error: %v", err)
266+
}
267+
268+
want := &VisualStudioSubscriptionAssignment{
269+
VisualStudioSubscriptionEmail: Ptr("user@example.com"),
270+
SubscriptionID: Ptr("sub-123"),
271+
Username: Ptr("monalisa"),
272+
ManualMatch: Ptr(true),
273+
}
274+
275+
if !cmp.Equal(assignment, want) {
276+
t.Errorf("Enterprise.AddOrUpdateVisualStudioSubscriptionAssignment returned %+v, want %+v", assignment, want)
277+
}
278+
279+
const methodName = "AddOrUpdateVisualStudioSubscriptionAssignment"
280+
testBadOptions(t, methodName, func() (err error) {
281+
_, _, err = client.Enterprise.AddOrUpdateVisualStudioSubscriptionAssignment(ctx, "\n", "sub-123", input)
282+
return err
283+
})
284+
285+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
286+
got, resp, err := client.Enterprise.AddOrUpdateVisualStudioSubscriptionAssignment(ctx, "e", "sub-123", input)
287+
if got != nil {
288+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
289+
}
290+
return resp, err
291+
})
292+
}
293+
294+
func TestEnterpriseService_DeleteVisualStudioSubscriptionAssignment(t *testing.T) {
295+
t.Parallel()
296+
client, mux, _ := setup(t)
297+
298+
mux.HandleFunc("/enterprises/e/visual-studio-subscriptions/sub-123", func(w http.ResponseWriter, r *http.Request) {
299+
testMethod(t, r, "DELETE")
300+
w.WriteHeader(http.StatusNoContent)
301+
})
302+
303+
ctx := t.Context()
304+
resp, err := client.Enterprise.DeleteVisualStudioSubscriptionAssignment(ctx, "e", "sub-123")
305+
if err != nil {
306+
t.Errorf("Enterprise.DeleteVisualStudioSubscriptionAssignment returned error: %v", err)
307+
}
308+
309+
if resp.StatusCode != http.StatusNoContent {
310+
t.Errorf("Enterprise.DeleteVisualStudioSubscriptionAssignment status code = %v, want %v", resp.StatusCode, http.StatusNoContent)
311+
}
312+
313+
const methodName = "DeleteVisualStudioSubscriptionAssignment"
314+
testBadOptions(t, methodName, func() (err error) {
315+
_, err = client.Enterprise.DeleteVisualStudioSubscriptionAssignment(ctx, "\n", "sub-123")
316+
return err
317+
})
318+
319+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
320+
return client.Enterprise.DeleteVisualStudioSubscriptionAssignment(ctx, "e", "sub-123")
321+
})
322+
}

github/github-accessors.go

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)