diff --git a/spec/github/models_spec.cr b/spec/github/models_spec.cr index e353faa..b32fd5a 100644 --- a/spec/github/models_spec.cr +++ b/spec/github/models_spec.cr @@ -59,6 +59,12 @@ describe Github::Subject do it "still uses latest_comment_url even for non-body types" do subject_from("Commit", url: "u", latest_comment_url: "c").comment_url.should eq "c" end + + # Release は subject.url を取得するとリリースノートが返る(issue #121)。 + it "falls back to url for a release when latest_comment_url is blank" do + subject = subject_from("Release", url: "https://api.github.com/repos/o/r/releases/1") + subject.comment_url.should eq "https://api.github.com/repos/o/r/releases/1" + end end describe "#commented?" do @@ -335,6 +341,42 @@ describe Github::Comment do Github::Comment.new(nil).commented?.should be_false end end + + # Release の応答は投稿者を author に入れる。user を必須にしていたころは解析ごと + # 失敗し、本文が「取得できませんでした」に倒れていた(issue #121)。 + describe "#poster" do + it "parses a release payload and keeps its body" do + comment = Github::Comment.from_json(release_payload) + + comment.body.should eq "リリースノート本文" + comment.poster.login.should eq "octocat" + end + + it "prefers the comment user over the author" do + json = {user: {login: "commenter"}, author: {login: "releaser"}, body: "b"}.to_json + Github::Comment.from_json(json).poster.login.should eq "commenter" + end + + it "falls back to an empty user when the payload has neither" do + Github::Comment.from_json({body: "b"}.to_json).poster.login.should be_nil + end + + it "returns an empty user for a locally built comment" do + Github::Comment.new("b").poster.login.should be_nil + end + end +end + +# GitHub の Release オブジェクト。投稿者は user ではなく author に入る。 +private def release_payload + { + url: "https://api.github.com/repos/o/r/releases/1", + html_url: "https://github.com/o/r/releases/tag/v1.2.3", + author: {login: "octocat", avatar_url: "https://example.com/a.png"}, + tag_name: "v1.2.3", + name: "v1.2.3", + body: "リリースノート本文", + }.to_json end private def notification_with(url = "", repo_html_url : String? = nil) diff --git a/spec/github/usecase_spec.cr b/spec/github/usecase_spec.cr index 847b644..f0fc346 100644 --- a/spec/github/usecase_spec.cr +++ b/spec/github/usecase_spec.cr @@ -51,6 +51,18 @@ private def subject_detail(comments : Int32? = nil, review_comments : Int32? = n }.to_json) end +# GitHub の Release オブジェクト。投稿者は user ではなく author に入る。 +private def release_payload + { + url: "https://api.github.com/repos/octocat/Hello-World/releases/1", + html_url: "https://github.com/octocat/Hello-World/releases/tag/v1.2.3", + author: {login: "octocat", avatar_url: "https://example.com/a.png"}, + tag_name: "v1.2.3", + name: "v1.2.3", + body: "リリースノート本文", + }.to_json +end + private def build(notify, comment) Github::Usecase.new(StubRepo.new(comment)).build_message(notify) end @@ -107,6 +119,21 @@ describe Github::Usecase do it "leaves text nil when there is no comment body" do build(notification, comment(body: nil)).text.should be_nil end + + # Release は投稿者が author に入るため、user を必須にしていたころは応答の解析に + # 失敗し、本文が「取得できませんでした」になっていた(issue #121)。 + it "keeps the release notes and author of a release notification" do + notify = notification( + url: "https://api.github.com/repos/octocat/Hello-World/releases/1", + reason: "subscribed", + type: Github::Subject::Type::RELEASE, + ) + message = build(notify, Github::Comment.from_json(release_payload)) + + message.text.should eq "リリースノート本文" + message.author_name.should eq "octocat" + message.title_link.should eq "https://github.com/octocat/Hello-World/releases/tag/v1.2.3" + end end describe "#build_message mention" do diff --git a/src/github/models.cr b/src/github/models.cr index 43f1efd..2ef8d3a 100644 --- a/src/github/models.cr +++ b/src/github/models.cr @@ -184,6 +184,7 @@ module Github ISSUE = "Issue" COMMIT = "Commit" DISCUSSION = "Discussion" + RELEASE = "Release" end UPDATE_TYPES = { @@ -207,6 +208,10 @@ module Github BODY_TYPES = { Type::PULL_REQUEST, Type::ISSUE, + # Release も subject.url を取得すると body(リリースノート)が返る。 + # 通知に latest_comment_url が入らない場合でも本文を出せるようにする + # (issue #121)。 + Type::RELEASE, } def update? : Bool @@ -278,7 +283,11 @@ module Github class Comment include JSON::Serializable - getter user : User + # 投稿者。コメントや Issue / PR では user だが、Release では author に入る。 + # 必須にすると Release の応答が丸ごと解析エラーになり、本文まで + # 「取得できませんでした」に倒れるため、どちらも nilable で受ける(issue #121)。 + getter user : User? + getter author : User? getter html_url : String? getter body : String? # スレッドのコメント数。subject 本体を取得したときだけ入り、コメント @@ -288,7 +297,12 @@ module Github getter review_comments : Int32? def initialize(@body) - @user = User.new + end + + # 表示に使う投稿者。どちらのキーも無い応答では空の User を返し、名前も + # アイコンも付けずに本文だけを出す。 + def poster : User + user || author || User.new end # スレッドにコメントが 1 件以上付いているか(issue #116)。 diff --git a/src/github/usecase.cr b/src/github/usecase.cr index 9e0188d..f5b8d62 100644 --- a/src/github/usecase.cr +++ b/src/github/usecase.cr @@ -18,9 +18,9 @@ module Github # 投稿の区切りは reason だけで決め、CI によるメンション抑止は反映しない # (issue #120。理由は Notify::Message#important? のコメント)。 important: notify.mention?, - author_name: comment.user.login, - author_icon: comment.user.avatar_url, - author_link: comment.user.html_url, + author_name: comment.poster.login, + author_icon: comment.poster.avatar_url, + author_link: comment.poster.html_url, # コメントが無いスレッドでは comment は subject 本体(PR / Issue)になる。 # その中のコメント数を文言の切り替え判定に使う(issue #116)。 pretext: notify.pretext(comment),