Skip to content

Commit dc807dd

Browse files
authored
feat: Add GitHub Classroom GetAssignment API endpoint (#3685)
1 parent 69dc04b commit dc807dd

File tree

6 files changed

+786
-0
lines changed

6 files changed

+786
-0
lines changed

github/classroom.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 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+
"fmt"
11+
"net/http"
12+
)
13+
14+
// ClassroomService handles communication with the GitHub Classroom related
15+
// methods of the GitHub API.
16+
//
17+
// GitHub API docs: https://docs.github.com/rest/classroom/classroom
18+
type ClassroomService service
19+
20+
// Classroom represents a GitHub Classroom.
21+
type Classroom struct {
22+
ID *int64 `json:"id,omitempty"`
23+
Name *string `json:"name,omitempty"`
24+
Archived *bool `json:"archived,omitempty"`
25+
Organization *Organization `json:"organization,omitempty"`
26+
URL *string `json:"url,omitempty"`
27+
}
28+
29+
func (c Classroom) String() string {
30+
return Stringify(c)
31+
}
32+
33+
// ClassroomAssignment represents a GitHub Classroom assignment.
34+
type ClassroomAssignment struct {
35+
ID *int64 `json:"id,omitempty"`
36+
PublicRepo *bool `json:"public_repo,omitempty"`
37+
Title *string `json:"title,omitempty"`
38+
Type *string `json:"type,omitempty"`
39+
InviteLink *string `json:"invite_link,omitempty"`
40+
InvitationsEnabled *bool `json:"invitations_enabled,omitempty"`
41+
Slug *string `json:"slug,omitempty"`
42+
StudentsAreRepoAdmins *bool `json:"students_are_repo_admins,omitempty"`
43+
FeedbackPullRequestsEnabled *bool `json:"feedback_pull_requests_enabled,omitempty"`
44+
MaxTeams *int `json:"max_teams,omitempty"`
45+
MaxMembers *int `json:"max_members,omitempty"`
46+
Editor *string `json:"editor,omitempty"`
47+
Accepted *int `json:"accepted,omitempty"`
48+
Submitted *int `json:"submitted,omitempty"`
49+
Passing *int `json:"passing,omitempty"`
50+
Language *string `json:"language,omitempty"`
51+
Deadline *Timestamp `json:"deadline,omitempty"`
52+
StarterCodeRepository *Repository `json:"starter_code_repository,omitempty"`
53+
Classroom *Classroom `json:"classroom,omitempty"`
54+
}
55+
56+
func (a ClassroomAssignment) String() string {
57+
return Stringify(a)
58+
}
59+
60+
// GetAssignment gets a GitHub Classroom assignment. Assignment will only be
61+
// returned if the current user is an administrator of the GitHub Classroom
62+
// for the assignment.
63+
//
64+
// GitHub API docs: https://docs.github.com/rest/classroom/classroom#get-an-assignment
65+
//
66+
//meta:operation GET /assignments/{assignment_id}
67+
func (s *ClassroomService) GetAssignment(ctx context.Context, assignmentID int64) (*ClassroomAssignment, *Response, error) {
68+
u := fmt.Sprintf("assignments/%v", assignmentID)
69+
70+
req, err := s.client.NewRequest(http.MethodGet, u, nil)
71+
if err != nil {
72+
return nil, nil, err
73+
}
74+
75+
assignment := new(ClassroomAssignment)
76+
resp, err := s.client.Do(ctx, req, assignment)
77+
if err != nil {
78+
return nil, resp, err
79+
}
80+
81+
return assignment, resp, nil
82+
}

github/classroom_test.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright 2025 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+
"fmt"
11+
"net/http"
12+
"testing"
13+
"time"
14+
15+
"github.com/google/go-cmp/cmp"
16+
)
17+
18+
func TestClassroom_Marshal(t *testing.T) {
19+
t.Parallel()
20+
testJSONMarshal(t, &Classroom{}, "{}")
21+
22+
c := &Classroom{
23+
ID: Ptr(int64(1296269)),
24+
Name: Ptr("Programming Elixir"),
25+
Archived: Ptr(false),
26+
Organization: &Organization{
27+
ID: Ptr(int64(1)),
28+
Login: Ptr("programming-elixir"),
29+
NodeID: Ptr("MDEyOk9yZ2FuaXphdGlvbjE="),
30+
HTMLURL: Ptr("https://github.com/programming-elixir"),
31+
Name: Ptr("Learn how to build fault tolerant applications"),
32+
AvatarURL: Ptr("https://avatars.githubusercontent.com/u/9919?v=4"),
33+
},
34+
URL: Ptr("https://classroom.github.com/classrooms/1-programming-elixir"),
35+
}
36+
37+
want := `{
38+
"id": 1296269,
39+
"name": "Programming Elixir",
40+
"archived": false,
41+
"organization": {
42+
"id": 1,
43+
"login": "programming-elixir",
44+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE=",
45+
"html_url": "https://github.com/programming-elixir",
46+
"name": "Learn how to build fault tolerant applications",
47+
"avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4"
48+
},
49+
"url": "https://classroom.github.com/classrooms/1-programming-elixir"
50+
}`
51+
52+
testJSONMarshal(t, c, want)
53+
}
54+
55+
func TestClassroomAssignment_Marshal(t *testing.T) {
56+
t.Parallel()
57+
testJSONMarshal(t, &ClassroomAssignment{}, "{}")
58+
59+
a := &ClassroomAssignment{
60+
ID: Ptr(int64(12)),
61+
PublicRepo: Ptr(false),
62+
Title: Ptr("Intro to Binaries"),
63+
Type: Ptr("individual"),
64+
InviteLink: Ptr("https://classroom.github.com/a/Lx7jiUgx"),
65+
InvitationsEnabled: Ptr(true),
66+
Slug: Ptr("intro-to-binaries"),
67+
StudentsAreRepoAdmins: Ptr(false),
68+
FeedbackPullRequestsEnabled: Ptr(true),
69+
MaxTeams: Ptr(0),
70+
MaxMembers: Ptr(0),
71+
Editor: Ptr("codespaces"),
72+
Accepted: Ptr(100),
73+
Submitted: Ptr(40),
74+
Passing: Ptr(10),
75+
Language: Ptr("ruby"),
76+
Deadline: &Timestamp{referenceTime},
77+
StarterCodeRepository: &Repository{
78+
ID: Ptr(int64(1296269)),
79+
FullName: Ptr("octocat/Hello-World"),
80+
},
81+
Classroom: &Classroom{
82+
ID: Ptr(int64(1296269)),
83+
Name: Ptr("Programming Elixir"),
84+
},
85+
}
86+
87+
want := `{
88+
"id": 12,
89+
"public_repo": false,
90+
"title": "Intro to Binaries",
91+
"type": "individual",
92+
"invite_link": "https://classroom.github.com/a/Lx7jiUgx",
93+
"invitations_enabled": true,
94+
"slug": "intro-to-binaries",
95+
"students_are_repo_admins": false,
96+
"feedback_pull_requests_enabled": true,
97+
"max_teams": 0,
98+
"max_members": 0,
99+
"editor": "codespaces",
100+
"accepted": 100,
101+
"submitted": 40,
102+
"passing": 10,
103+
"language": "ruby",
104+
"deadline": ` + referenceTimeStr + `,
105+
"starter_code_repository": {
106+
"id": 1296269,
107+
"full_name": "octocat/Hello-World"
108+
},
109+
"classroom": {
110+
"id": 1296269,
111+
"name": "Programming Elixir"
112+
}
113+
}`
114+
115+
testJSONMarshal(t, a, want)
116+
}
117+
118+
func TestClassroomService_GetAssignment(t *testing.T) {
119+
t.Parallel()
120+
client, mux, _ := setup(t)
121+
122+
mux.HandleFunc("/assignments/12", func(w http.ResponseWriter, r *http.Request) {
123+
testMethod(t, r, "GET")
124+
fmt.Fprint(w, `{
125+
"id": 12,
126+
"public_repo": false,
127+
"title": "Intro to Binaries",
128+
"type": "individual",
129+
"invite_link": "https://classroom.github.com/a/Lx7jiUgx",
130+
"invitations_enabled": true,
131+
"slug": "intro-to-binaries",
132+
"students_are_repo_admins": false,
133+
"feedback_pull_requests_enabled": true,
134+
"max_teams": 0,
135+
"max_members": 0,
136+
"editor": "codespaces",
137+
"accepted": 100,
138+
"submitted": 40,
139+
"passing": 10,
140+
"language": "ruby",
141+
"deadline": "2011-01-26T19:06:43Z",
142+
"starter_code_repository": {
143+
"id": 1296269,
144+
"full_name": "octocat/Hello-World",
145+
"html_url": "https://github.com/octocat/Hello-World",
146+
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
147+
"private": false,
148+
"default_branch": "main"
149+
},
150+
"classroom": {
151+
"id": 1296269,
152+
"name": "Programming Elixir",
153+
"archived": false,
154+
"url": "https://classroom.github.com/classrooms/1-programming-elixir"
155+
}
156+
}`)
157+
})
158+
159+
ctx := context.Background()
160+
assignment, _, err := client.Classroom.GetAssignment(ctx, 12)
161+
if err != nil {
162+
t.Errorf("Classroom.GetAssignment returned error: %v", err)
163+
}
164+
165+
want := &ClassroomAssignment{
166+
ID: Ptr(int64(12)),
167+
PublicRepo: Ptr(false),
168+
Title: Ptr("Intro to Binaries"),
169+
Type: Ptr("individual"),
170+
InviteLink: Ptr("https://classroom.github.com/a/Lx7jiUgx"),
171+
InvitationsEnabled: Ptr(true),
172+
Slug: Ptr("intro-to-binaries"),
173+
StudentsAreRepoAdmins: Ptr(false),
174+
FeedbackPullRequestsEnabled: Ptr(true),
175+
MaxTeams: Ptr(0),
176+
MaxMembers: Ptr(0),
177+
Editor: Ptr("codespaces"),
178+
Accepted: Ptr(100),
179+
Submitted: Ptr(40),
180+
Passing: Ptr(10),
181+
Language: Ptr("ruby"),
182+
Deadline: func() *Timestamp { t, _ := time.Parse(time.RFC3339, "2011-01-26T19:06:43Z"); return &Timestamp{t} }(),
183+
StarterCodeRepository: &Repository{
184+
ID: Ptr(int64(1296269)),
185+
FullName: Ptr("octocat/Hello-World"),
186+
HTMLURL: Ptr("https://github.com/octocat/Hello-World"),
187+
NodeID: Ptr("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"),
188+
Private: Ptr(false),
189+
DefaultBranch: Ptr("main"),
190+
},
191+
Classroom: &Classroom{
192+
ID: Ptr(int64(1296269)),
193+
Name: Ptr("Programming Elixir"),
194+
Archived: Ptr(false),
195+
URL: Ptr("https://classroom.github.com/classrooms/1-programming-elixir"),
196+
},
197+
}
198+
199+
if !cmp.Equal(assignment, want) {
200+
t.Errorf("Classroom.GetAssignment returned %+v, want %+v", assignment, want)
201+
}
202+
203+
const methodName = "GetAssignment"
204+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
205+
got, resp, err := client.Classroom.GetAssignment(ctx, 12)
206+
if got != nil {
207+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
208+
}
209+
return resp, err
210+
})
211+
}

0 commit comments

Comments
 (0)