55"encoding/json"
66"net/http"
77"testing"
8- "time"
98
109"github.com/github/github-mcp-server/internal/githubv4mock"
1110"github.com/github/github-mcp-server/pkg/translations"
@@ -17,75 +16,89 @@ import (
1716
1817var (
1918discussionsGeneral = []map [string ]any {
20- {"number" :1 ,"title" :"Discussion 1 title" ,"createdAt" :"2023-01-01T00:00:00Z" ,"updatedAt" :"2023-01-01T00:00:00Z" ,"author" :map [string ]any {"login" :"user1" },"url" :"https://github.com/owner/repo/discussions/1" ,"category" :map [string ]any {"name" :"General" }},
21- {"number" :3 ,"title" :"Discussion 3 title" ,"createdAt" :"2023-03-01T00:00:00Z" ,"updatedAt" :"2023-02-01T00:00:00Z" ,"author" :map [string ]any {"login" :"user1" },"url" :"https://github.com/owner/repo/discussions/3" ,"category" :map [string ]any {"name" :"General" }},
19+ {"number" :1 ,"title" :"Discussion 1 title" ,"createdAt" :"2023-01-01T00:00:00Z" ,"updatedAt" :"2023-01-01T00:00:00Z" ,"closed" : false , "isAnswered" : false , " author" :map [string ]any {"login" :"user1" },"url" :"https://github.com/owner/repo/discussions/1" ,"category" :map [string ]any {"name" :"General" }},
20+ {"number" :3 ,"title" :"Discussion 3 title" ,"createdAt" :"2023-03-01T00:00:00Z" ,"updatedAt" :"2023-02-01T00:00:00Z" ,"closed" : false , "isAnswered" : false , " author" :map [string ]any {"login" :"user1" },"url" :"https://github.com/owner/repo/discussions/3" ,"category" :map [string ]any {"name" :"General" }},
2221}
2322discussionsAll = []map [string ]any {
2423{
25- "number" :1 ,
26- "title" :"Discussion 1 title" ,
27- "createdAt" :"2023-01-01T00:00:00Z" ,
28- "updatedAt" :"2023-01-01T00:00:00Z" ,
29- "author" :map [string ]any {"login" :"user1" },
30- "url" :"https://github.com/owner/repo/discussions/1" ,
31- "category" :map [string ]any {"name" :"General" },
24+ "number" :1 ,
25+ "title" :"Discussion 1 title" ,
26+ "createdAt" :"2023-01-01T00:00:00Z" ,
27+ "updatedAt" :"2023-01-01T00:00:00Z" ,
28+ "closed" :false ,
29+ "isAnswered" :false ,
30+ "author" :map [string ]any {"login" :"user1" },
31+ "url" :"https://github.com/owner/repo/discussions/1" ,
32+ "category" :map [string ]any {"name" :"General" },
3233},
3334{
34- "number" :2 ,
35- "title" :"Discussion 2 title" ,
36- "createdAt" :"2023-02-01T00:00:00Z" ,
37- "updatedAt" :"2023-02-01T00:00:00Z" ,
38- "author" :map [string ]any {"login" :"user2" },
39- "url" :"https://github.com/owner/repo/discussions/2" ,
40- "category" :map [string ]any {"name" :"Questions" },
35+ "number" :2 ,
36+ "title" :"Discussion 2 title" ,
37+ "createdAt" :"2023-02-01T00:00:00Z" ,
38+ "updatedAt" :"2023-02-01T00:00:00Z" ,
39+ "closed" :false ,
40+ "isAnswered" :false ,
41+ "author" :map [string ]any {"login" :"user2" },
42+ "url" :"https://github.com/owner/repo/discussions/2" ,
43+ "category" :map [string ]any {"name" :"Questions" },
4144},
4245{
43- "number" :3 ,
44- "title" :"Discussion 3 title" ,
45- "createdAt" :"2023-03-01T00:00:00Z" ,
46- "updatedAt" :"2023-03-01T00:00:00Z" ,
47- "author" :map [string ]any {"login" :"user3" },
48- "url" :"https://github.com/owner/repo/discussions/3" ,
49- "category" :map [string ]any {"name" :"General" },
46+ "number" :3 ,
47+ "title" :"Discussion 3 title" ,
48+ "createdAt" :"2023-03-01T00:00:00Z" ,
49+ "updatedAt" :"2023-03-01T00:00:00Z" ,
50+ "closed" :false ,
51+ "isAnswered" :false ,
52+ "author" :map [string ]any {"login" :"user3" },
53+ "url" :"https://github.com/owner/repo/discussions/3" ,
54+ "category" :map [string ]any {"name" :"General" },
5055},
5156}
5257
5358discussionsOrgLevel = []map [string ]any {
5459{
55- "number" :1 ,
56- "title" :"Org Discussion 1 - Community Guidelines" ,
57- "createdAt" :"2023-01-15T00:00:00Z" ,
58- "updatedAt" :"2023-01-15T00:00:00Z" ,
59- "author" :map [string ]any {"login" :"org-admin" },
60- "url" :"https://github.com/owner/.github/discussions/1" ,
61- "category" :map [string ]any {"name" :"Announcements" },
60+ "number" :1 ,
61+ "title" :"Org Discussion 1 - Community Guidelines" ,
62+ "createdAt" :"2023-01-15T00:00:00Z" ,
63+ "updatedAt" :"2023-01-15T00:00:00Z" ,
64+ "closed" :false ,
65+ "isAnswered" :false ,
66+ "author" :map [string ]any {"login" :"org-admin" },
67+ "url" :"https://github.com/owner/.github/discussions/1" ,
68+ "category" :map [string ]any {"name" :"Announcements" },
6269},
6370{
64- "number" :2 ,
65- "title" :"Org Discussion 2 - Roadmap 2023" ,
66- "createdAt" :"2023-02-20T00:00:00Z" ,
67- "updatedAt" :"2023-02-20T00:00:00Z" ,
68- "author" :map [string ]any {"login" :"org-admin" },
69- "url" :"https://github.com/owner/.github/discussions/2" ,
70- "category" :map [string ]any {"name" :"General" },
71+ "number" :2 ,
72+ "title" :"Org Discussion 2 - Roadmap 2023" ,
73+ "createdAt" :"2023-02-20T00:00:00Z" ,
74+ "updatedAt" :"2023-02-20T00:00:00Z" ,
75+ "closed" :false ,
76+ "isAnswered" :false ,
77+ "author" :map [string ]any {"login" :"org-admin" },
78+ "url" :"https://github.com/owner/.github/discussions/2" ,
79+ "category" :map [string ]any {"name" :"General" },
7180},
7281{
73- "number" :3 ,
74- "title" :"Org Discussion 3 - Roadmap 2024" ,
75- "createdAt" :"2023-02-20T00:00:00Z" ,
76- "updatedAt" :"2023-02-20T00:00:00Z" ,
77- "author" :map [string ]any {"login" :"org-admin" },
78- "url" :"https://github.com/owner/.github/discussions/3" ,
79- "category" :map [string ]any {"name" :"General" },
82+ "number" :3 ,
83+ "title" :"Org Discussion 3 - Roadmap 2024" ,
84+ "createdAt" :"2023-02-20T00:00:00Z" ,
85+ "updatedAt" :"2023-02-20T00:00:00Z" ,
86+ "closed" :false ,
87+ "isAnswered" :false ,
88+ "author" :map [string ]any {"login" :"org-admin" },
89+ "url" :"https://github.com/owner/.github/discussions/3" ,
90+ "category" :map [string ]any {"name" :"General" },
8091},
8192{
82- "number" :4 ,
83- "title" :"Org Discussion 4 - Roadmap 2025" ,
84- "createdAt" :"2023-02-20T00:00:00Z" ,
85- "updatedAt" :"2023-02-20T00:00:00Z" ,
86- "author" :map [string ]any {"login" :"org-admin" },
87- "url" :"https://github.com/owner/.github/discussions/4" ,
88- "category" :map [string ]any {"name" :"General" },
93+ "number" :4 ,
94+ "title" :"Org Discussion 4 - Roadmap 2025" ,
95+ "createdAt" :"2023-02-20T00:00:00Z" ,
96+ "updatedAt" :"2023-02-20T00:00:00Z" ,
97+ "closed" :false ,
98+ "isAnswered" :false ,
99+ "author" :map [string ]any {"login" :"org-admin" },
100+ "url" :"https://github.com/owner/.github/discussions/4" ,
101+ "category" :map [string ]any {"name" :"General" },
89102},
90103}
91104
@@ -388,10 +401,10 @@ func Test_ListDiscussions(t *testing.T) {
388401}
389402
390403// Define the actual query strings that match the implementation
391- qBasicNoOrder := "query($after:String$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
392- qWithCategoryNoOrder := "query($after:String$categoryId:ID!$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
393- qBasicWithOrder := "query($after:String$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
394- qWithCategoryAndOrder := "query($after:String$categoryId:ID!$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
404+ qBasicNoOrder := "query($after:String$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt, author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
405+ qWithCategoryNoOrder := "query($after:String$categoryId:ID!$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt, author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
406+ qBasicWithOrder := "query($after:String$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt, author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
407+ qWithCategoryAndOrder := "query($after:String$categoryId:ID!$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt, author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
395408
396409for _ ,tc := range tests {
397410t .Run (tc .name ,func (t * testing.T ) {
@@ -484,7 +497,7 @@ func Test_GetDiscussion(t *testing.T) {
484497assert .ElementsMatch (t ,toolDef .InputSchema .Required , []string {"owner" ,"repo" ,"discussionNumber" })
485498
486499// Use exact string query that matches implementation output
487- qGetDiscussion := "query($discussionNumber:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussion(number: $discussionNumber){number,title,body,createdAt,url,category{name}}}}"
500+ qGetDiscussion := "query($discussionNumber:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussion(number: $discussionNumber){number,title,body,createdAt,closed,isAnswered,answerChosenAt, url,category{name}}}}"
488501
489502vars := map [string ]interface {}{
490503"owner" :"owner" ,
@@ -495,31 +508,31 @@ func Test_GetDiscussion(t *testing.T) {
495508name string
496509response githubv4mock.GQLResponse
497510expectError bool
498- expected * github. Discussion
511+ expected map [ string ] interface {}
499512errContains string
500513}{
501514{
502515name :"successful retrieval" ,
503516response :githubv4mock .DataResponse (map [string ]any {
504517"repository" :map [string ]any {"discussion" :map [string ]any {
505- "number" :1 ,
506- "title" :"Test Discussion Title" ,
507- "body" :"This is a test discussion" ,
508- "url" :"https://github.com/owner/repo/discussions/1" ,
509- "createdAt" :"2025-04-25T12:00:00Z" ,
510- "category" :map [string ]any {"name" :"General" },
518+ "number" :1 ,
519+ "title" :"Test Discussion Title" ,
520+ "body" :"This is a test discussion" ,
521+ "url" :"https://github.com/owner/repo/discussions/1" ,
522+ "createdAt" :"2025-04-25T12:00:00Z" ,
523+ "closed" :false ,
524+ "isAnswered" :false ,
525+ "category" :map [string ]any {"name" :"General" },
511526}},
512527}),
513528expectError :false ,
514- expected :& github.Discussion {
515- HTMLURL :github .Ptr ("https://github.com/owner/repo/discussions/1" ),
516- Number :github .Ptr (1 ),
517- Title :github .Ptr ("Test Discussion Title" ),
518- Body :github .Ptr ("This is a test discussion" ),
519- CreatedAt :& github.Timestamp {Time :time .Date (2025 ,4 ,25 ,12 ,0 ,0 ,0 ,time .UTC )},
520- DiscussionCategory :& github.DiscussionCategory {
521- Name :github .Ptr ("General" ),
522- },
529+ expected :map [string ]interface {}{
530+ "number" :float64 (1 ),
531+ "title" :"Test Discussion Title" ,
532+ "body" :"This is a test discussion" ,
533+ "url" :"https://github.com/owner/repo/discussions/1" ,
534+ "closed" :false ,
535+ "isAnswered" :false ,
523536},
524537},
525538{
@@ -547,14 +560,18 @@ func Test_GetDiscussion(t *testing.T) {
547560}
548561
549562require .NoError (t ,err )
550- var out github. Discussion
563+ var out map [ string ] interface {}
551564require .NoError (t ,json .Unmarshal ([]byte (text ),& out ))
552- assert .Equal (t ,* tc .expected .HTMLURL ,* out .HTMLURL )
553- assert .Equal (t ,* tc .expected .Number ,* out .Number )
554- assert .Equal (t ,* tc .expected .Title ,* out .Title )
555- assert .Equal (t ,* tc .expected .Body ,* out .Body )
556- // Check category label
557- assert .Equal (t ,* tc .expected .DiscussionCategory .Name ,* out .DiscussionCategory .Name )
565+ assert .Equal (t ,tc .expected ["number" ],out ["number" ])
566+ assert .Equal (t ,tc .expected ["title" ],out ["title" ])
567+ assert .Equal (t ,tc .expected ["body" ],out ["body" ])
568+ assert .Equal (t ,tc .expected ["url" ],out ["url" ])
569+ assert .Equal (t ,tc .expected ["closed" ],out ["closed" ])
570+ assert .Equal (t ,tc .expected ["isAnswered" ],out ["isAnswered" ])
571+ // Check category is present
572+ category ,ok := out ["category" ].(map [string ]interface {})
573+ require .True (t ,ok )
574+ assert .Equal (t ,"General" ,category ["name" ])
558575})
559576}
560577}