From 132df79c37d51098eec0a2d9250e8bff65e46293 Mon Sep 17 00:00:00 2001 From: jferrl Date: Wed, 24 Sep 2025 10:00:05 +0200 Subject: [PATCH 1/2] chore: Add ListAcceptedAssignments and GetAssignmentGrades methods to Classroom API --- github/classroom.go | 87 ++++++ github/classroom_test.go | 462 ++++++++++++++++++++++++++++++++ github/github-accessors.go | 144 ++++++++++ github/github-accessors_test.go | 192 +++++++++++++ github/github-stringify_test.go | 38 +++ 5 files changed, 923 insertions(+) diff --git a/github/classroom.go b/github/classroom.go index 5c44cc0d406..f92471a16a9 100644 --- a/github/classroom.go +++ b/github/classroom.go @@ -56,6 +56,41 @@ func (a ClassroomAssignment) String() string { return Stringify(a) } +// AcceptedAssignment represents a GitHub Classroom accepted assignment. +type AcceptedAssignment struct { + ID *int64 `json:"id,omitempty"` + Submitted *bool `json:"submitted,omitempty"` + Passing *bool `json:"passing,omitempty"` + CommitCount *int `json:"commit_count,omitempty"` + Grade *string `json:"grade,omitempty"` + Students []*User `json:"students,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Assignment *ClassroomAssignment `json:"assignment,omitempty"` +} + +func (a AcceptedAssignment) String() string { + return Stringify(a) +} + +// AssignmentGrade represents a GitHub Classroom assignment grade. +type AssignmentGrade struct { + AssignmentName *string `json:"assignment_name,omitempty"` + AssignmentURL *string `json:"assignment_url,omitempty"` + StarterCodeURL *string `json:"starter_code_url,omitempty"` + GithubUsername *string `json:"github_username,omitempty"` + RosterIdentifier *string `json:"roster_identifier,omitempty"` + StudentRepositoryName *string `json:"student_repository_name,omitempty"` + StudentRepositoryURL *string `json:"student_repository_url,omitempty"` + SubmissionTimestamp *Timestamp `json:"submission_timestamp,omitempty"` + PointsAwarded *int `json:"points_awarded,omitempty"` + PointsAvailable *int `json:"points_available,omitempty"` + GroupName *string `json:"group_name,omitempty"` +} + +func (g AssignmentGrade) String() string { + return Stringify(g) +} + // GetAssignment gets a GitHub Classroom assignment. Assignment will only be // returned if the current user is an administrator of the GitHub Classroom // for the assignment. @@ -155,3 +190,55 @@ func (s *ClassroomService) ListClassroomAssignments(ctx context.Context, classro return assignments, resp, nil } + +// ListAcceptedAssignments lists accepted assignments for a GitHub Classroom assignment. +// Accepted assignments will only be returned if the current user is an administrator +// of the GitHub Classroom for the assignment. +// +// GitHub API docs: https://docs.github.com/rest/classroom/classroom#list-accepted-assignments-for-an-assignment +// +//meta:operation GET /assignments/{assignment_id}/accepted_assignments +func (s *ClassroomService) ListAcceptedAssignments(ctx context.Context, assignmentID int64, opts *ListOptions) ([]*AcceptedAssignment, *Response, error) { + u := fmt.Sprintf("assignments/%v/accepted_assignments", assignmentID) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var acceptedAssignments []*AcceptedAssignment + resp, err := s.client.Do(ctx, req, &acceptedAssignments) + if err != nil { + return nil, resp, err + } + + return acceptedAssignments, resp, nil +} + +// GetAssignmentGrades gets assignment grades for a GitHub Classroom assignment. +// Grades will only be returned if the current user is an administrator +// of the GitHub Classroom for the assignment. +// +// GitHub API docs: https://docs.github.com/rest/classroom/classroom#get-assignment-grades +// +//meta:operation GET /assignments/{assignment_id}/grades +func (s *ClassroomService) GetAssignmentGrades(ctx context.Context, assignmentID int64) ([]*AssignmentGrade, *Response, error) { + u := fmt.Sprintf("assignments/%v/grades", assignmentID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var grades []*AssignmentGrade + resp, err := s.client.Do(ctx, req, &grades) + if err != nil { + return nil, resp, err + } + + return grades, resp, nil +} diff --git a/github/classroom_test.go b/github/classroom_test.go index eaacd8143d5..9f1fb556e8e 100644 --- a/github/classroom_test.go +++ b/github/classroom_test.go @@ -115,6 +115,146 @@ func TestClassroomAssignment_Marshal(t *testing.T) { testJSONMarshal(t, a, want) } +func TestAcceptedAssignment_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &AcceptedAssignment{}, "{}") + + a := &AcceptedAssignment{ + ID: Ptr(int64(42)), + Submitted: Ptr(true), + Passing: Ptr(true), + CommitCount: Ptr(5), + Grade: Ptr("10/10"), + Students: []*User{ + { + ID: Ptr(int64(1)), + Login: Ptr("octocat"), + AvatarURL: Ptr("https://github.com/images/error/octocat_happy.gif"), + HTMLURL: Ptr("https://github.com/octocat"), + }, + }, + Repository: &Repository{ + ID: Ptr(int64(1296269)), + FullName: Ptr("octocat/Hello-World"), + HTMLURL: Ptr("https://github.com/octocat/Hello-World"), + NodeID: Ptr("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"), + Private: Ptr(false), + DefaultBranch: Ptr("main"), + }, + Assignment: &ClassroomAssignment{ + ID: Ptr(int64(12)), + PublicRepo: Ptr(false), + Title: Ptr("Intro to Binaries"), + Type: Ptr("individual"), + InviteLink: Ptr("https://example.com/a/Lx7jiUgx"), + InvitationsEnabled: Ptr(true), + Slug: Ptr("intro-to-binaries"), + StudentsAreRepoAdmins: Ptr(false), + FeedbackPullRequestsEnabled: Ptr(true), + MaxTeams: Ptr(0), + MaxMembers: Ptr(0), + Editor: Ptr("codespaces"), + Accepted: Ptr(100), + Submitted: Ptr(40), + Passing: Ptr(10), + Language: Ptr("ruby"), + Deadline: &Timestamp{referenceTime}, + Classroom: &Classroom{ + ID: Ptr(int64(1296269)), + Name: Ptr("Programming Elixir"), + Archived: Ptr(false), + URL: Ptr("https://example.com/classrooms/programming"), + }, + }, + } + + want := `{ + "id": 42, + "submitted": true, + "passing": true, + "commit_count": 5, + "grade": "10/10", + "students": [ + { + "id": 1, + "login": "octocat", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "html_url": "https://github.com/octocat" + } + ], + "repository": { + "id": 1296269, + "full_name": "octocat/Hello-World", + "html_url": "https://github.com/octocat/Hello-World", + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "private": false, + "default_branch": "main" + }, + "assignment": { + "id": 12, + "public_repo": false, + "title": "Intro to Binaries", + "type": "individual", + "invite_link": "https://example.com/a/Lx7jiUgx", + "invitations_enabled": true, + "slug": "intro-to-binaries", + "students_are_repo_admins": false, + "feedback_pull_requests_enabled": true, + "max_teams": 0, + "max_members": 0, + "editor": "codespaces", + "accepted": 100, + "submitted": 40, + "passing": 10, + "language": "ruby", + "deadline": "2006-01-02T15:04:05Z", + "classroom": { + "id": 1296269, + "name": "Programming Elixir", + "archived": false, + "url": "https://example.com/classrooms/programming" + } + } + }` + + testJSONMarshal(t, a, want) +} + +func TestAssignmentGrade_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &AssignmentGrade{}, "{}") + + g := &AssignmentGrade{ + AssignmentName: Ptr("Intro to Binaries"), + AssignmentURL: Ptr("https://classroom.github.com/assignments/12"), + StarterCodeURL: Ptr("https://github.com/octocat/Hello-World"), + GithubUsername: Ptr("octocat"), + RosterIdentifier: Ptr("student123"), + StudentRepositoryName: Ptr("octocat/intro-to-binaries"), + StudentRepositoryURL: Ptr("https://github.com/octocat/intro-to-binaries"), + SubmissionTimestamp: &Timestamp{referenceTime}, + PointsAwarded: Ptr(10), + PointsAvailable: Ptr(10), + GroupName: Ptr("Team Alpha"), + } + + want := `{ + "assignment_name": "Intro to Binaries", + "assignment_url": "https://classroom.github.com/assignments/12", + "starter_code_url": "https://github.com/octocat/Hello-World", + "github_username": "octocat", + "roster_identifier": "student123", + "student_repository_name": "octocat/intro-to-binaries", + "student_repository_url": "https://github.com/octocat/intro-to-binaries", + "submission_timestamp": "2006-01-02T15:04:05Z", + "points_awarded": 10, + "points_available": 10, + "group_name": "Team Alpha" + }` + + testJSONMarshal(t, g, want) +} + func TestClassroomService_GetAssignment(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -475,3 +615,325 @@ func TestClassroomService_ListClassroomAssignments(t *testing.T) { return resp, err }) } + +func TestClassroomService_ListAcceptedAssignments(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/assignments/12/accepted_assignments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2", "per_page": "2"}) + fmt.Fprint(w, `[ + { + "id": 42, + "submitted": true, + "passing": true, + "commit_count": 5, + "grade": "10/10", + "students": [ + { + "id": 1, + "login": "octocat", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "html_url": "https://github.com/octocat" + } + ], + "repository": { + "id": 1296269, + "full_name": "octocat/Hello-World", + "html_url": "https://github.com/octocat/Hello-World", + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "private": false, + "default_branch": "main" + }, + "assignment": { + "id": 12, + "public_repo": false, + "title": "Intro to Binaries", + "type": "individual", + "invite_link": "https://example.com/a/Lx7jiUgx", + "invitations_enabled": true, + "slug": "intro-to-binaries", + "students_are_repo_admins": false, + "feedback_pull_requests_enabled": true, + "max_teams": 0, + "max_members": 0, + "editor": "codespaces", + "accepted": 100, + "submitted": 40, + "passing": 10, + "language": "ruby", + "deadline": "2011-01-26T19:06:43Z", + "classroom": { + "id": 1296269, + "name": "Programming Elixir", + "archived": false, + "url": "https://example.com/classrooms/programming" + } + } + }, + { + "id": 43, + "submitted": false, + "passing": false, + "commit_count": 2, + "grade": "5/10", + "students": [ + { + "id": 2, + "login": "monalisa", + "avatar_url": "https://github.com/images/error/monalisa_happy.gif", + "html_url": "https://github.com/monalisa" + } + ], + "repository": { + "id": 1296270, + "full_name": "monalisa/Hello-World", + "html_url": "https://github.com/monalisa/Hello-World", + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2Mjcw", + "private": true, + "default_branch": "main" + }, + "assignment": { + "id": 12, + "public_repo": false, + "title": "Intro to Binaries", + "type": "individual", + "invite_link": "https://example.com/a/Lx7jiUgx", + "invitations_enabled": true, + "slug": "intro-to-binaries", + "students_are_repo_admins": false, + "feedback_pull_requests_enabled": true, + "max_teams": 0, + "max_members": 0, + "editor": "codespaces", + "accepted": 100, + "submitted": 40, + "passing": 10, + "language": "ruby", + "deadline": "2011-01-26T19:06:43Z", + "classroom": { + "id": 1296269, + "name": "Programming Elixir", + "archived": false, + "url": "https://example.com/classrooms/programming" + } + } + } + ]`) + }) + + ctx := context.Background() + opt := &ListOptions{Page: 2, PerPage: 2} + acceptedAssignments, _, err := client.Classroom.ListAcceptedAssignments(ctx, 12, opt) + if err != nil { + t.Errorf("Classroom.ListAcceptedAssignments returned error: %v", err) + } + + want := []*AcceptedAssignment{ + { + ID: Ptr(int64(42)), + Submitted: Ptr(true), + Passing: Ptr(true), + CommitCount: Ptr(5), + Grade: Ptr("10/10"), + Students: []*User{ + { + ID: Ptr(int64(1)), + Login: Ptr("octocat"), + AvatarURL: Ptr("https://github.com/images/error/octocat_happy.gif"), + HTMLURL: Ptr("https://github.com/octocat"), + }, + }, + Repository: &Repository{ + ID: Ptr(int64(1296269)), + FullName: Ptr("octocat/Hello-World"), + HTMLURL: Ptr("https://github.com/octocat/Hello-World"), + NodeID: Ptr("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"), + Private: Ptr(false), + DefaultBranch: Ptr("main"), + }, + Assignment: &ClassroomAssignment{ + ID: Ptr(int64(12)), + PublicRepo: Ptr(false), + Title: Ptr("Intro to Binaries"), + Type: Ptr("individual"), + InviteLink: Ptr("https://example.com/a/Lx7jiUgx"), + InvitationsEnabled: Ptr(true), + Slug: Ptr("intro-to-binaries"), + StudentsAreRepoAdmins: Ptr(false), + FeedbackPullRequestsEnabled: Ptr(true), + MaxTeams: Ptr(0), + MaxMembers: Ptr(0), + Editor: Ptr("codespaces"), + Accepted: Ptr(100), + Submitted: Ptr(40), + Passing: Ptr(10), + Language: Ptr("ruby"), + Deadline: &Timestamp{time.Date(2011, 1, 26, 19, 6, 43, 0, time.UTC)}, + Classroom: &Classroom{ + ID: Ptr(int64(1296269)), + Name: Ptr("Programming Elixir"), + Archived: Ptr(false), + URL: Ptr("https://example.com/classrooms/programming"), + }, + }, + }, + { + ID: Ptr(int64(43)), + Submitted: Ptr(false), + Passing: Ptr(false), + CommitCount: Ptr(2), + Grade: Ptr("5/10"), + Students: []*User{ + { + ID: Ptr(int64(2)), + Login: Ptr("monalisa"), + AvatarURL: Ptr("https://github.com/images/error/monalisa_happy.gif"), + HTMLURL: Ptr("https://github.com/monalisa"), + }, + }, + Repository: &Repository{ + ID: Ptr(int64(1296270)), + FullName: Ptr("monalisa/Hello-World"), + HTMLURL: Ptr("https://github.com/monalisa/Hello-World"), + NodeID: Ptr("MDEwOlJlcG9zaXRvcnkxMjk2Mjcw"), + Private: Ptr(true), + DefaultBranch: Ptr("main"), + }, + Assignment: &ClassroomAssignment{ + ID: Ptr(int64(12)), + PublicRepo: Ptr(false), + Title: Ptr("Intro to Binaries"), + Type: Ptr("individual"), + InviteLink: Ptr("https://example.com/a/Lx7jiUgx"), + InvitationsEnabled: Ptr(true), + Slug: Ptr("intro-to-binaries"), + StudentsAreRepoAdmins: Ptr(false), + FeedbackPullRequestsEnabled: Ptr(true), + MaxTeams: Ptr(0), + MaxMembers: Ptr(0), + Editor: Ptr("codespaces"), + Accepted: Ptr(100), + Submitted: Ptr(40), + Passing: Ptr(10), + Language: Ptr("ruby"), + Deadline: &Timestamp{time.Date(2011, 1, 26, 19, 6, 43, 0, time.UTC)}, + Classroom: &Classroom{ + ID: Ptr(int64(1296269)), + Name: Ptr("Programming Elixir"), + Archived: Ptr(false), + URL: Ptr("https://example.com/classrooms/programming"), + }, + }, + }, + } + + if !cmp.Equal(acceptedAssignments, want) { + t.Errorf("Classroom.ListAcceptedAssignments returned %+v, want %+v", acceptedAssignments, want) + } + + const methodName = "ListAcceptedAssignments" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Classroom.ListAcceptedAssignments(ctx, -1, opt) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Classroom.ListAcceptedAssignments(ctx, 12, opt) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestClassroomService_GetAssignmentGrades(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/assignments/12/grades", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "assignment_name": "Intro to Binaries", + "assignment_url": "https://classroom.github.com/assignments/12", + "starter_code_url": "https://github.com/octocat/Hello-World", + "github_username": "octocat", + "roster_identifier": "student123", + "student_repository_name": "octocat/intro-to-binaries", + "student_repository_url": "https://github.com/octocat/intro-to-binaries", + "submission_timestamp": "2011-01-26T19:06:43Z", + "points_awarded": 10, + "points_available": 10, + "group_name": "Team Alpha" + }, + { + "assignment_name": "Intro to Binaries", + "assignment_url": "https://classroom.github.com/assignments/12", + "starter_code_url": "https://github.com/octocat/Hello-World", + "github_username": "monalisa", + "roster_identifier": "student456", + "student_repository_name": "monalisa/intro-to-binaries", + "student_repository_url": "https://github.com/monalisa/intro-to-binaries", + "submission_timestamp": "2011-01-27T10:30:15Z", + "points_awarded": 8, + "points_available": 10, + "group_name": "Team Beta" + } + ]`) + }) + + ctx := context.Background() + grades, _, err := client.Classroom.GetAssignmentGrades(ctx, 12) + if err != nil { + t.Errorf("Classroom.GetAssignmentGrades returned error: %v", err) + } + + want := []*AssignmentGrade{ + { + AssignmentName: Ptr("Intro to Binaries"), + AssignmentURL: Ptr("https://classroom.github.com/assignments/12"), + StarterCodeURL: Ptr("https://github.com/octocat/Hello-World"), + GithubUsername: Ptr("octocat"), + RosterIdentifier: Ptr("student123"), + StudentRepositoryName: Ptr("octocat/intro-to-binaries"), + StudentRepositoryURL: Ptr("https://github.com/octocat/intro-to-binaries"), + SubmissionTimestamp: &Timestamp{time.Date(2011, 1, 26, 19, 6, 43, 0, time.UTC)}, + PointsAwarded: Ptr(10), + PointsAvailable: Ptr(10), + GroupName: Ptr("Team Alpha"), + }, + { + AssignmentName: Ptr("Intro to Binaries"), + AssignmentURL: Ptr("https://classroom.github.com/assignments/12"), + StarterCodeURL: Ptr("https://github.com/octocat/Hello-World"), + GithubUsername: Ptr("monalisa"), + RosterIdentifier: Ptr("student456"), + StudentRepositoryName: Ptr("monalisa/intro-to-binaries"), + StudentRepositoryURL: Ptr("https://github.com/monalisa/intro-to-binaries"), + SubmissionTimestamp: &Timestamp{time.Date(2011, 1, 27, 10, 30, 15, 0, time.UTC)}, + PointsAwarded: Ptr(8), + PointsAvailable: Ptr(10), + GroupName: Ptr("Team Beta"), + }, + } + + if !cmp.Equal(grades, want) { + t.Errorf("Classroom.GetAssignmentGrades returned %+v, want %+v", grades, want) + } + + const methodName = "GetAssignmentGrades" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Classroom.GetAssignmentGrades(ctx, -1) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Classroom.GetAssignmentGrades(ctx, 12) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index b470ba80255..5383186ff90 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -22,6 +22,62 @@ func (a *AbuseRateLimitError) GetRetryAfter() time.Duration { return *a.RetryAfter } +// GetAssignment returns the Assignment field. +func (a *AcceptedAssignment) GetAssignment() *ClassroomAssignment { + if a == nil { + return nil + } + return a.Assignment +} + +// GetCommitCount returns the CommitCount field if it's non-nil, zero value otherwise. +func (a *AcceptedAssignment) GetCommitCount() int { + if a == nil || a.CommitCount == nil { + return 0 + } + return *a.CommitCount +} + +// GetGrade returns the Grade field if it's non-nil, zero value otherwise. +func (a *AcceptedAssignment) GetGrade() string { + if a == nil || a.Grade == nil { + return "" + } + return *a.Grade +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (a *AcceptedAssignment) GetID() int64 { + if a == nil || a.ID == nil { + return 0 + } + return *a.ID +} + +// GetPassing returns the Passing field if it's non-nil, zero value otherwise. +func (a *AcceptedAssignment) GetPassing() bool { + if a == nil || a.Passing == nil { + return false + } + return *a.Passing +} + +// GetRepository returns the Repository field. +func (a *AcceptedAssignment) GetRepository() *Repository { + if a == nil { + return nil + } + return a.Repository +} + +// GetSubmitted returns the Submitted field if it's non-nil, zero value otherwise. +func (a *AcceptedAssignment) GetSubmitted() bool { + if a == nil || a.Submitted == nil { + return false + } + return *a.Submitted +} + // GetGithubOwnedAllowed returns the GithubOwnedAllowed field if it's non-nil, zero value otherwise. func (a *ActionsAllowed) GetGithubOwnedAllowed() bool { if a == nil || a.GithubOwnedAllowed == nil { @@ -1086,6 +1142,94 @@ func (a *ArtifactWorkflowRun) GetRepositoryID() int64 { return *a.RepositoryID } +// GetAssignmentName returns the AssignmentName field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetAssignmentName() string { + if a == nil || a.AssignmentName == nil { + return "" + } + return *a.AssignmentName +} + +// GetAssignmentURL returns the AssignmentURL field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetAssignmentURL() string { + if a == nil || a.AssignmentURL == nil { + return "" + } + return *a.AssignmentURL +} + +// GetGithubUsername returns the GithubUsername field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetGithubUsername() string { + if a == nil || a.GithubUsername == nil { + return "" + } + return *a.GithubUsername +} + +// GetGroupName returns the GroupName field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetGroupName() string { + if a == nil || a.GroupName == nil { + return "" + } + return *a.GroupName +} + +// GetPointsAvailable returns the PointsAvailable field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetPointsAvailable() int { + if a == nil || a.PointsAvailable == nil { + return 0 + } + return *a.PointsAvailable +} + +// GetPointsAwarded returns the PointsAwarded field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetPointsAwarded() int { + if a == nil || a.PointsAwarded == nil { + return 0 + } + return *a.PointsAwarded +} + +// GetRosterIdentifier returns the RosterIdentifier field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetRosterIdentifier() string { + if a == nil || a.RosterIdentifier == nil { + return "" + } + return *a.RosterIdentifier +} + +// GetStarterCodeURL returns the StarterCodeURL field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetStarterCodeURL() string { + if a == nil || a.StarterCodeURL == nil { + return "" + } + return *a.StarterCodeURL +} + +// GetStudentRepositoryName returns the StudentRepositoryName field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetStudentRepositoryName() string { + if a == nil || a.StudentRepositoryName == nil { + return "" + } + return *a.StudentRepositoryName +} + +// GetStudentRepositoryURL returns the StudentRepositoryURL field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetStudentRepositoryURL() string { + if a == nil || a.StudentRepositoryURL == nil { + return "" + } + return *a.StudentRepositoryURL +} + +// GetSubmissionTimestamp returns the SubmissionTimestamp field if it's non-nil, zero value otherwise. +func (a *AssignmentGrade) GetSubmissionTimestamp() Timestamp { + if a == nil || a.SubmissionTimestamp == nil { + return Timestamp{} + } + return *a.SubmissionTimestamp +} + // GetBody returns the Body field if it's non-nil, zero value otherwise. func (a *Attachment) GetBody() string { if a == nil || a.Body == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index d61b00e5f69..f8e5d3466dd 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -26,6 +26,77 @@ func TestAbuseRateLimitError_GetRetryAfter(tt *testing.T) { a.GetRetryAfter() } +func TestAcceptedAssignment_GetAssignment(tt *testing.T) { + tt.Parallel() + a := &AcceptedAssignment{} + a.GetAssignment() + a = nil + a.GetAssignment() +} + +func TestAcceptedAssignment_GetCommitCount(tt *testing.T) { + tt.Parallel() + var zeroValue int + a := &AcceptedAssignment{CommitCount: &zeroValue} + a.GetCommitCount() + a = &AcceptedAssignment{} + a.GetCommitCount() + a = nil + a.GetCommitCount() +} + +func TestAcceptedAssignment_GetGrade(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AcceptedAssignment{Grade: &zeroValue} + a.GetGrade() + a = &AcceptedAssignment{} + a.GetGrade() + a = nil + a.GetGrade() +} + +func TestAcceptedAssignment_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + a := &AcceptedAssignment{ID: &zeroValue} + a.GetID() + a = &AcceptedAssignment{} + a.GetID() + a = nil + a.GetID() +} + +func TestAcceptedAssignment_GetPassing(tt *testing.T) { + tt.Parallel() + var zeroValue bool + a := &AcceptedAssignment{Passing: &zeroValue} + a.GetPassing() + a = &AcceptedAssignment{} + a.GetPassing() + a = nil + a.GetPassing() +} + +func TestAcceptedAssignment_GetRepository(tt *testing.T) { + tt.Parallel() + a := &AcceptedAssignment{} + a.GetRepository() + a = nil + a.GetRepository() +} + +func TestAcceptedAssignment_GetSubmitted(tt *testing.T) { + tt.Parallel() + var zeroValue bool + a := &AcceptedAssignment{Submitted: &zeroValue} + a.GetSubmitted() + a = &AcceptedAssignment{} + a.GetSubmitted() + a = nil + a.GetSubmitted() +} + func TestActionsAllowed_GetGithubOwnedAllowed(tt *testing.T) { tt.Parallel() var zeroValue bool @@ -1411,6 +1482,127 @@ func TestArtifactWorkflowRun_GetRepositoryID(tt *testing.T) { a.GetRepositoryID() } +func TestAssignmentGrade_GetAssignmentName(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{AssignmentName: &zeroValue} + a.GetAssignmentName() + a = &AssignmentGrade{} + a.GetAssignmentName() + a = nil + a.GetAssignmentName() +} + +func TestAssignmentGrade_GetAssignmentURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{AssignmentURL: &zeroValue} + a.GetAssignmentURL() + a = &AssignmentGrade{} + a.GetAssignmentURL() + a = nil + a.GetAssignmentURL() +} + +func TestAssignmentGrade_GetGithubUsername(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{GithubUsername: &zeroValue} + a.GetGithubUsername() + a = &AssignmentGrade{} + a.GetGithubUsername() + a = nil + a.GetGithubUsername() +} + +func TestAssignmentGrade_GetGroupName(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{GroupName: &zeroValue} + a.GetGroupName() + a = &AssignmentGrade{} + a.GetGroupName() + a = nil + a.GetGroupName() +} + +func TestAssignmentGrade_GetPointsAvailable(tt *testing.T) { + tt.Parallel() + var zeroValue int + a := &AssignmentGrade{PointsAvailable: &zeroValue} + a.GetPointsAvailable() + a = &AssignmentGrade{} + a.GetPointsAvailable() + a = nil + a.GetPointsAvailable() +} + +func TestAssignmentGrade_GetPointsAwarded(tt *testing.T) { + tt.Parallel() + var zeroValue int + a := &AssignmentGrade{PointsAwarded: &zeroValue} + a.GetPointsAwarded() + a = &AssignmentGrade{} + a.GetPointsAwarded() + a = nil + a.GetPointsAwarded() +} + +func TestAssignmentGrade_GetRosterIdentifier(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{RosterIdentifier: &zeroValue} + a.GetRosterIdentifier() + a = &AssignmentGrade{} + a.GetRosterIdentifier() + a = nil + a.GetRosterIdentifier() +} + +func TestAssignmentGrade_GetStarterCodeURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{StarterCodeURL: &zeroValue} + a.GetStarterCodeURL() + a = &AssignmentGrade{} + a.GetStarterCodeURL() + a = nil + a.GetStarterCodeURL() +} + +func TestAssignmentGrade_GetStudentRepositoryName(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{StudentRepositoryName: &zeroValue} + a.GetStudentRepositoryName() + a = &AssignmentGrade{} + a.GetStudentRepositoryName() + a = nil + a.GetStudentRepositoryName() +} + +func TestAssignmentGrade_GetStudentRepositoryURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + a := &AssignmentGrade{StudentRepositoryURL: &zeroValue} + a.GetStudentRepositoryURL() + a = &AssignmentGrade{} + a.GetStudentRepositoryURL() + a = nil + a.GetStudentRepositoryURL() +} + +func TestAssignmentGrade_GetSubmissionTimestamp(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + a := &AssignmentGrade{SubmissionTimestamp: &zeroValue} + a.GetSubmissionTimestamp() + a = &AssignmentGrade{} + a.GetSubmissionTimestamp() + a = nil + a.GetSubmissionTimestamp() +} + func TestAttachment_GetBody(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index b246f0bc35a..9585b9923e9 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -13,6 +13,23 @@ import ( "testing" ) +func TestAcceptedAssignment_String(t *testing.T) { + t.Parallel() + v := AcceptedAssignment{ + ID: Ptr(int64(0)), + Submitted: Ptr(false), + Passing: Ptr(false), + CommitCount: Ptr(0), + Grade: Ptr(""), + Repository: &Repository{}, + Assignment: &ClassroomAssignment{}, + } + want := `github.AcceptedAssignment{ID:0, Submitted:false, Passing:false, CommitCount:0, Grade:"", Repository:github.Repository{}, Assignment:github.ClassroomAssignment{}}` + if got := v.String(); got != want { + t.Errorf("AcceptedAssignment.String = %v, want %v", got, want) + } +} + func TestActionsAllowed_String(t *testing.T) { t.Parallel() v := ActionsAllowed{ @@ -108,6 +125,27 @@ func TestArtifactPeriod_String(t *testing.T) { } } +func TestAssignmentGrade_String(t *testing.T) { + t.Parallel() + v := AssignmentGrade{ + AssignmentName: Ptr(""), + AssignmentURL: Ptr(""), + StarterCodeURL: Ptr(""), + GithubUsername: Ptr(""), + RosterIdentifier: Ptr(""), + StudentRepositoryName: Ptr(""), + StudentRepositoryURL: Ptr(""), + SubmissionTimestamp: &Timestamp{}, + PointsAwarded: Ptr(0), + PointsAvailable: Ptr(0), + GroupName: Ptr(""), + } + want := `github.AssignmentGrade{AssignmentName:"", AssignmentURL:"", StarterCodeURL:"", GithubUsername:"", RosterIdentifier:"", StudentRepositoryName:"", StudentRepositoryURL:"", SubmissionTimestamp:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PointsAwarded:0, PointsAvailable:0, GroupName:""}` + if got := v.String(); got != want { + t.Errorf("AssignmentGrade.String = %v, want %v", got, want) + } +} + func TestAuthorization_String(t *testing.T) { t.Parallel() v := Authorization{ From 9bce5fc2c4b5a9a679fe5d2603889bd0e332c0a0 Mon Sep 17 00:00:00 2001 From: jferrl Date: Thu, 25 Sep 2025 22:00:51 +0200 Subject: [PATCH 2/2] chore: create ClassroomUser type --- github/classroom.go | 14 ++++++++++- github/classroom_test.go | 6 ++--- github/github-accessors.go | 32 ++++++++++++++++++++++++ github/github-accessors_test.go | 44 +++++++++++++++++++++++++++++++++ github/github-stringify_test.go | 14 +++++++++++ 5 files changed, 106 insertions(+), 4 deletions(-) diff --git a/github/classroom.go b/github/classroom.go index f92471a16a9..47124dd4a0e 100644 --- a/github/classroom.go +++ b/github/classroom.go @@ -16,6 +16,18 @@ import ( // GitHub API docs: https://docs.github.com/rest/classroom/classroom type ClassroomService service +// ClassroomUser represents a GitHub user simplified for Classroom. +type ClassroomUser struct { + ID *int64 `json:"id,omitempty"` + Login *string `json:"login,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` +} + +func (u ClassroomUser) String() string { + return Stringify(u) +} + // Classroom represents a GitHub Classroom. type Classroom struct { ID *int64 `json:"id,omitempty"` @@ -63,7 +75,7 @@ type AcceptedAssignment struct { Passing *bool `json:"passing,omitempty"` CommitCount *int `json:"commit_count,omitempty"` Grade *string `json:"grade,omitempty"` - Students []*User `json:"students,omitempty"` + Students []*ClassroomUser `json:"students,omitempty"` Repository *Repository `json:"repository,omitempty"` Assignment *ClassroomAssignment `json:"assignment,omitempty"` } diff --git a/github/classroom_test.go b/github/classroom_test.go index 9f1fb556e8e..d60b1dc2f82 100644 --- a/github/classroom_test.go +++ b/github/classroom_test.go @@ -125,7 +125,7 @@ func TestAcceptedAssignment_Marshal(t *testing.T) { Passing: Ptr(true), CommitCount: Ptr(5), Grade: Ptr("10/10"), - Students: []*User{ + Students: []*ClassroomUser{ { ID: Ptr(int64(1)), Login: Ptr("octocat"), @@ -737,7 +737,7 @@ func TestClassroomService_ListAcceptedAssignments(t *testing.T) { Passing: Ptr(true), CommitCount: Ptr(5), Grade: Ptr("10/10"), - Students: []*User{ + Students: []*ClassroomUser{ { ID: Ptr(int64(1)), Login: Ptr("octocat"), @@ -785,7 +785,7 @@ func TestClassroomService_ListAcceptedAssignments(t *testing.T) { Passing: Ptr(false), CommitCount: Ptr(2), Grade: Ptr("5/10"), - Students: []*User{ + Students: []*ClassroomUser{ { ID: Ptr(int64(2)), Login: Ptr("monalisa"), diff --git a/github/github-accessors.go b/github/github-accessors.go index 5383186ff90..101bdc69718 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -2862,6 +2862,38 @@ func (c *ClassroomAssignment) GetType() string { return *c.Type } +// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise. +func (c *ClassroomUser) GetAvatarURL() string { + if c == nil || c.AvatarURL == nil { + return "" + } + return *c.AvatarURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *ClassroomUser) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *ClassroomUser) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (c *ClassroomUser) GetLogin() string { + if c == nil || c.Login == nil { + return "" + } + return *c.Login +} + // GetFingerprint returns the Fingerprint field if it's non-nil, zero value otherwise. func (c *ClusterSSHKey) GetFingerprint() string { if c == nil || c.Fingerprint == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index f8e5d3466dd..80f315473a1 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -3724,6 +3724,50 @@ func TestClassroomAssignment_GetType(tt *testing.T) { c.GetType() } +func TestClassroomUser_GetAvatarURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ClassroomUser{AvatarURL: &zeroValue} + c.GetAvatarURL() + c = &ClassroomUser{} + c.GetAvatarURL() + c = nil + c.GetAvatarURL() +} + +func TestClassroomUser_GetHTMLURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ClassroomUser{HTMLURL: &zeroValue} + c.GetHTMLURL() + c = &ClassroomUser{} + c.GetHTMLURL() + c = nil + c.GetHTMLURL() +} + +func TestClassroomUser_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + c := &ClassroomUser{ID: &zeroValue} + c.GetID() + c = &ClassroomUser{} + c.GetID() + c = nil + c.GetID() +} + +func TestClassroomUser_GetLogin(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ClassroomUser{Login: &zeroValue} + c.GetLogin() + c = &ClassroomUser{} + c.GetLogin() + c = nil + c.GetLogin() +} + func TestClusterSSHKey_GetFingerprint(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index 9585b9923e9..217f5e32936 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -310,6 +310,20 @@ func TestClassroomAssignment_String(t *testing.T) { } } +func TestClassroomUser_String(t *testing.T) { + t.Parallel() + v := ClassroomUser{ + ID: Ptr(int64(0)), + Login: Ptr(""), + AvatarURL: Ptr(""), + HTMLURL: Ptr(""), + } + want := `github.ClassroomUser{ID:0, Login:"", AvatarURL:"", HTMLURL:""}` + if got := v.String(); got != want { + t.Errorf("ClassroomUser.String = %v, want %v", got, want) + } +} + func TestCodeOfConduct_String(t *testing.T) { t.Parallel() v := CodeOfConduct{