<|start_filename|>test/repo.coffee<|end_filename|>
[ gh, assert, nock, mock_robot ] = require "./test_helper"

describe "repo api", ->
  describe "branches", ->
    response = [ { name: "foo", commit: { sha: "abcdeg", url: "xxx" } } ]
    network = null
    success = (done) ->
      (body) ->
        network.done()
        done()
    beforeEach ->
      network = nock("https://api.github.com")
        .get("/repos/foo/bar/branches")
        .reply(200, response)
    it "accepts a full repo", (done) ->
      gh.branches "foo/bar", success done
    it "accepts an unqualified repo", (done) ->
      process.env.HUBOT_GITHUB_USER = "foo"
      gh.branches "bar", success done
      delete process.env.HUBOT_GITHUB_USER
    it "returns json", (done) ->
      gh.branches "foo/bar", (data) ->
        assert.deepEqual response, data
        done()
    it "allows per-request overrides", (done) ->
      network = nock("https://special.api.dev")
        .get("/repos/bar/baz/branches")
        .reply(200, response)
      gh.withOptions(
          apiRoot: "https://special.api.dev"
          defaultUser: "bar"
          defaultRepo: "baz"
        )
        .branches null, success done
      delete process.env.HUBOT_GITHUB_USER

    describe "create", ->
      beforeEach ->
        @branchName = "newbranch"
        @masterSha = "aaaa9999"
        network = nock("https://api.github.com")
          .get("/repos/foo/bar/git/refs/heads/master")
          .reply(
            200
            , { object: { type: "commit", sha: @masterSha, url: "zzz" }
              , url: "zzz"
              , ref: "refs/heads/master"
            }
          )
          .post("/repos/foo/bar/git/refs",
            ref: "refs/heads/#{@branchName}", sha: @masterSha )
          .reply(
            200
            , { object: { type: "commit", sha: "hijklmn", url: "xxx" }
              , url: "yyy"
              , ref: "refs/heads/#{@branchName}"
            }
          )
      it "returns json", (done) ->
        gh.branches( "foo/bar" ).create @branchName, (data) =>
          assert.deepEqual data,
            name: @branchName, commit: {sha: "hijklmn", url: "xxx"}
          network.done()
          done()

    describe "create from another branch", ->
      beforeEach ->
        @toBranch = "newbranch"
        @fromBranch = "oldbranch"
        @branchSha = "bbbbcccc"
        network = nock("https://api.github.com")
          .get("/repos/foo/bar/git/refs/heads/#{@fromBranch}")
          .reply(
            200
            , { object: { type: "commit", sha: @branchSha, url: "zzz" }
              , url: "zzz"
              , ref: "refs/heads/#{@fromBranch}"
            }
          )
          .post("/repos/foo/bar/git/refs",
            ref: "refs/heads/#{@toBranch}", sha: @branchSha )
          .reply(
            200
            , { object: { type: "commit", sha: "dddd", url: "aaa" }
              , url: "bbb"
              , ref: "refs/heads/#{@toBranch}"
            }
          )
      it "returns json", (done) ->
        gh.branches( "foo/bar" ).create @toBranch, from: @fromBranch, (data) =>
          assert.deepEqual data,
            name: @toBranch, commit: {sha: "dddd", url: "aaa"}
          network.done()
          done()

    describe "delete", ->
      beforeEach ->
        @branchName = "badbranch"
        @response = { object: { type: "commit", sha: "hijklmn", url: "xxx" }, url: "yyy", "ref": "refs/heads/#{@branchName}" }
        network = nock("https://api.github.com")
          .delete("/repos/foo/bar/git/refs/heads/#{@branchName}")
          .reply(204, {})
      it "returns nothing", (done) ->
        gh.branches( "foo/bar" ).delete @branchName, ->
          network.done()
          done()
      it "accepts multiple branch names", (done) ->
        network.delete("/repos/foo/bar/git/refs/heads/anotherBranch")
          .reply(204, {})
        gh.branches( "foo/bar" ).delete @branchName, "anotherBranch", ->
          process.nextTick ->
            network.done()
            done()

    describe "merge", ->
      beforeEach ->
        @branchName = "newbranch"
        @sha = "deadbeef"

      it "succeeds", (done) ->
        network = nock("https://api.github.com")
          .post("/repos/foo/bar/merges",
            base: "master", head: @branchName)
          .reply(
            201
            , sha: @sha, url: "xyz", commit: { message: "commit message" }
          )
        gh.branches("foo/bar").merge @branchName, (commit) =>
          assert.deepEqual commit,
            sha: @sha, message: "commit message", url: "xyz"
          network.done()
          done()

      context "with base specified", (done) ->
        beforeEach ->
          @base = "targetbranch"
          network = nock("https://api.github.com")
            .post("/repos/foo/bar/merges",
              base: @base, head: @branchName)
            .reply(
              201
              , sha: @sha, url: "xyz", commit: { message: "commit message" }
            )
          @cb = (done) => (commit) =>
            assert.deepEqual commit,
              sha: @sha, message: "commit message", url: "xyz"
            network.done()
            done()

        it "as 'into'", (done) ->
          gh.branches("foo/bar").merge @branchName, {into: @base}, @cb(done)
        it "as 'base'", (done) ->
          gh.branches("foo/bar").merge @branchName, {base: @base}, @cb(done)

      it "with commit message specified", (done) ->
        @message = "An awesome merge!"
        network = nock("https://api.github.com")
          .post("/repos/foo/bar/merges",
            base: "master", head: @branchName, commit_message: @message)
          .reply(
            201
            , sha: @sha, url: "xyz", commit: { message: @message }
          )
        gh.branches("foo/bar").merge @branchName, message: @message, (commit) =>
          assert.deepEqual commit,
            sha: @sha, message: @message, url: "xyz"
          network.done()
          done()

      context "when no-op", (done) ->
        beforeEach ->
          network = nock("https://api.github.com")
            .post("/repos/foo/bar/merges",
              base: "master", head: @branchName)
            .reply(204)

        it "errors", (done) ->
          gh.branches("foo/bar").merge @branchName, ->
            assert.fail null, null, "Should not call callback"
          mock_robot.onError = (msg) ->
            assert.ok /nothing to merge/i.exec msg
            network.done()
            done()

        it "notifies custom error handler", (done) ->
          errHandler = (response) ->
            assert.ok /nothing to merge/i.exec response.error
            network.done()
            done()
          gh.withOptions(errorHandler: errHandler).branches("foo/bar").merge @branchName

  describe "deployments", ->
    describe "create deployment", ->
      beforeEach ->
        @branchName = "newbranch"
        @payload = '{"environment":"production","deploy_user":"atmos","room_id":123456}'
        @description = "deploying my sweet branch"

      it "succeeds", (done) ->
        network = nock("https://api.github.com")
          .post("/repos/foo/bar/deployments",
            payload: @payload, ref: @branchName, description: @description)
          .reply(
            201
            , sha: @branchName, url: "xyz/1", description: "abc"
          )
        gh.deployments("foo/bar").create @branchName, {payload: @payload, description: @description}, (status) =>
          assert.deepEqual status,
            sha: @branchName, description: "abc", url: "xyz/1"
          network.done()
          done()

    describe "get deployment status", ->
      beforeEach ->
        @statusId = "123"

      it "succeeds", (done) ->
        network = nock("https://api.github.com")
          .get("/repos/foo/bar/deployments/#{@statusId}/statuses")
          .reply(
            201
            , [{id: @statusId, state: "success", url: "xyz/1", description: "abc"}]
          )
        gh.deployments("foo/bar").status @statusId, (status) =>
          assert.deepEqual status[0],
            id: @statusId, state: "success", url: "xyz/1", description: "abc"
          network.done()
          done()

      it "uses special version header", (done) ->
        network = nock("https://api.github.com")
          .get("/repos/foo/bar/deployments/#{@statusId}/statuses")
          .matchHeader('Accept', 'application/vnd.github.cannonball-preview+json')
          .reply(
            201
            , [{id: @statusId, state: "success", url: "xyz/1", description: "abc"}]
          )
        gh.deployments("foo/bar").status @statusId, (status) ->
          network.done()
          done()


<|start_filename|>test/qualified_repo.coffee<|end_filename|>
[ gh, assert, nock, mock_robot ] = require "./test_helper"

describe "qualified_repo", ->
  it "returns full name when given full name", ->
    assert.equal "foo/bar", gh.qualified_repo "foo/bar"
  it "converts to lower case", ->
    assert.equal "foo/bar", gh.qualified_repo "FoO/BAR"
  it "retrieves github username from env", ->
    process.env.HUBOT_GITHUB_USER = "watson"
    assert.equal "watson/bar", gh.qualified_repo "bar"
    delete process.env.HUBOT_GITHUB_USER
  it "retrieves github user&repo from env", ->
    process.env.HUBOT_GITHUB_USER = "watson"
    process.env.HUBOT_GITHUB_REPO = "baz"
    assert.equal "watson/baz", gh.qualified_repo null
    delete process.env.HUBOT_GITHUB_REPO
    delete process.env.HUBOT_GITHUB_USER
  it "retrieves fully qualified github repo from env", ->
    process.env.HUBOT_GITHUB_USER = "watson"
    process.env.HUBOT_GITHUB_REPO = "sherlock/bar"
    assert.equal "sherlock/bar", gh.qualified_repo null
    assert.equal "sherlock/bar", gh.qualified_repo()
    delete process.env.HUBOT_GITHUB_USER
    delete process.env.HUBOT_GITHUB_REPO
  it "complains when no username available", ->
    assert.equal "bar", gh.qualified_repo "bar"
    assert.ok /github user/i.exec mock_robot.logs.error.pop()
  it "complains when no repo available", ->
    assert.equal null, gh.qualified_repo null
    assert.ok /github repo/i.exec mock_robot.logs.error.pop()


<|start_filename|>test/acceptance/windowing.coffee<|end_filename|>
[ gh, assert, nock, mock_robot ] = require "../test_helper"
http = require "http"

# nock screws up the parsed URL options in node 0.4, and I can't be bothered
# to deal with the problem.
return if process.versions.node < "0.5.0"

describe "concurrent requests", ->
  it "are limited", (done) ->
    maxRequests = 0
    remain = 100
    port = 7329
    active = 0

    server = http.createServer (req, res) ->
      active++
      assert.ok active <= 35
      maxRequests = Math.max maxRequests, active
      setTimeout ->
        active--
        res.end()
      , 10
    server.listen port

    process.env.HUBOT_GITHUB_API = "http://localhost:#{port}"
    process.env.HUBOT_CONCURRENT_REQUESTS = 35
    gh = require("../..") mock_robot
    for i in [1..remain]
      gh.request "GET", "/repos/foo/bar/branches/#{i}", ->
        if --remain is 0
          assert.equal 35, maxRequests
          done()
    delete process.env.HUBOT_GITHUB_API
    delete process.env.HUBOT_CONCURRENT_REQUESTS


<|start_filename|>test/request.coffee<|end_filename|>
[ gh, assert, nock, mock_robot ] = require "./test_helper"

describe "github api", ->
  describe "general purpose", ->
    network = null
    success = (done) ->
      (body) ->
        network.done()
        done()
    describe "request", ->
      response = [ { name: "foo", commit: { sha: "abcdef", url: "xxx" } } ]
      beforeEach ->
        network = nock("https://api.github.com")
          .get("/repos/foo/bar/branches")
          .reply(200, response)
      it "accepts a full url", (done) ->
        gh.request "GET", "https://api.github.com/repos/foo/bar/branches", success done
      it "accepts a path", (done) ->
        gh.request "GET", "repos/foo/bar/branches", success done
      it "accepts a path (leading slash)", (done) ->
        gh.request "GET", "repos/foo/bar/branches", success done
      it "includes oauth token if exists", (done) ->
        process.env.HUBOT_GITHUB_TOKEN = "<PASSWORD>"
        network.matchHeader("Authorization", "token <PASSWORD>")
        gh.request "GET", "repos/foo/bar/branches", success done
        delete process.env.HUBOT_GITHUB_TOKEN
      it "includes accept header", (done) ->
        network.matchHeader('Accept', 'application/vnd.github.v3+json')
        gh.request "GET", "repos/foo/bar/branches", success done
      it "allows setting API version", (done) ->
        ghPreview = require("../src/githubot") mock_robot, apiVersion: 'preview'
        network.matchHeader('Accept', 'application/vnd.github.preview+json')
        ghPreview.request "GET", "repos/foo/bar/branches", success done
      it "allows setting API version for single request", (done) ->
        network.matchHeader('Accept', 'application/vnd.github.special+json')
        gh.withOptions(apiVersion: 'special').request "GET", "repos/foo/bar/branches", success done
      it "allows setting an API version for single request without robot", (done) ->
        noRobot = require("../src/githubot")
        newGh = noRobot.withOptions('Accept', 'application/vnd.github.preview+json')
        newGh.request "GET", "repos/foo/bar/branches", success done
      it "allows setting the oauth token for single request", (done) ->
        process.env.HUBOT_GITHUB_TOKEN = "<PASSWORD>"
        network.matchHeader("Authorization", "token abc")
        gh.withOptions(token: 'abc').request "GET", "repos/foo/bar/branches", success done
        delete process.env.HUBOT_GITHUB_TOKEN
      it "doesn't persist per-request options", (done) ->
        network.matchHeader('Accept', 'application/vnd.github.special+json')
        gh.withOptions(apiVersion: 'special').request "GET", "repos/foo/bar/branches", ->
          network.done()
          network2 = nock("https://api.github.com")
            .get("/repos/baz/bar/branches")
            .matchHeader('Accept', 'application/vnd.github.v3+json')
            .reply(200, response)
          # Should revert to the defaults on this request
          gh.request "GET", "repos/baz/bar/branches", ->
            network2.done()
            done()
      it "includes User-Agent header", (done) ->
        network.matchHeader('User-Agent', /GitHubot\/\d+\.\d+\.\d+/)
        gh.request "GET", "repos/foo/bar/branches", success done
      it "returns parsed json", (done) ->
        gh.request "GET", "repos/foo/bar/branches", (data) ->
          assert.deepEqual response, data
          done()
      context "custom base URL", ->
        beforeEach ->
          network = nock("http://mygithub.internal")
            .get("/repos/foo/bar/branches")
            .reply(200, response)
        it "is used if option exists", (done) ->
          process.env.HUBOT_GITHUB_API = "http://mygithub.internal"
          gh.request "GET", "repos/foo/bar/branches", success done
          delete process.env.HUBOT_GITHUB_API
        it "is used if passed explicitly", (done) ->
          gh.request "GET", "http://mygithub.internal/repos/foo/bar/branches", success done

    describe "get", ->
      beforeEach ->
        network = nock("https://api.github.com")
          .get("/gists")
          .reply(200, [])
      it "sends request", (done) ->
        gh.get "gists", success done

      describe "with params", ->
        beforeEach ->
          network = nock("https://api.github.com")
            .get("/users/foo/repos?foo=bar")
            .reply(200, [])
        it "accepts query params in url", (done) ->
          gh.get "https://api.github.com/users/foo/repos?foo=bar", success done
        it "accepts query params as hash", (done) ->
          gh.get "users/foo/repos", {foo: "bar"}, success done

    describe "post", ->
      data = description: "A test gist", public: true, files: { "abc.txt": { content: "abcdefg" } }
      response = url: "http://api.github.com/gists/1", id: 1
      beforeEach ->
        network = nock("https://api.github.com")
          .post("/gists", data)
          .reply(201, response)
      it "sends request", (done) ->
        gh.post "gists", data, success done

    describe "put", ->
      data = description: "A test gist", public: true, files: { "abc.txt": { content: "abcdefg" } }
      response = url: "http://api.github.com/gists/1", id: 1
      beforeEach ->
        network = nock("https://api.github.com")
          .put("/gists", data)
          .reply(201, response)
      it "sends request", (done) ->
        gh.put "gists", data, success done

    describe "patch", ->
      data = description: "A test gist", public: true, files: { "abc.txt": { content: "abcdefg" } }
      response = url: "http://api.github.com/gists/1", id: 1
      beforeEach ->
        network = nock("https://api.github.com")
          .patch("/gists", data)
          .reply(201, response)
      it "sends request", (done) ->
        gh.patch "gists", data, success done

    describe "delete", ->
      it "sends request", (done) ->
        network = nock("https://api.github.com")
          .delete("/gists/345")
          .reply(204)
        gh.delete "gists/345", success done
      it "includes empty body", (done) ->
        network = nock("https://api.github.com")
          .delete("/gists/345", "")
          .matchHeader("Content-Length", 0)
          .reply(204)
        gh.delete "gists/345", success done

  describe "errors", ->
    network = null
    http = require "scoped-http-client"
    never_called = ->
      assert.fail(null, null, "Success callback should not be invoked")
    beforeEach ->
      network = nock("https://api.github.com").get("/foo")
    afterEach ->
      mock_robot.onError = null
    kablooie = ->
      mock = {
        header: -> mock,
        get: () -> (cb) ->
          cb new Error "Kablooie!"
      }
      http._old_create = http.create
      http.create = -> mock
    afterEach ->
      if http._old_create?
        http.create = http._old_create
        http._old_create = null

    it "complains about failed response", (done) ->
      network.reply(401, message: "Bad credentials")
      mock_robot.onError = (msg) ->
        assert.ok /bad credentials/i.exec msg
        done()
      gh.get "/foo", never_called
    it "complains about bad response", (done) ->
      network.reply(500, "WTF$$%@! SERVER VOMIT")
      mock_robot.onError = (msg) ->
        assert.ok /vomit/i.exec msg
        done()
      gh.get "/foo", never_called
    it "complains about client errors", (done) ->
      kablooie()
      mock_robot.onError = (msg) ->
        assert.ok /kablooie/i.exec msg
        done()
      gh.get "/foo", never_called

    describe "with error handler", ->
      defaultHandler = gh._errorHandler
      beforeEach ->
        network = nock("https://api.github.com")
          .get("/foo")
      afterEach ->
        gh._errorHandler = defaultHandler

      it "calls handler on error", (done) ->
        network.reply(406, message: "I hate you!")
        gh.handleErrors (response) ->
          assert.equal 406, response.statusCode
          assert.equal "I hate you!", response.error
          assert.equal '{"message":"I hate you!"}', response.body
          done()
        gh.get "/foo", never_called

      it "doesn't call handler on success", (done) ->
        network.reply(201, message: "Hooray!")
        gh.handleErrors never_called
        gh.get "/foo", -> done()

      it "passes body if can't parse response", (done) ->
        network.reply(500, "WTF$$%@! SERVER VOMIT")
        gh.handleErrors (response) ->
          assert.equal 500, response.statusCode
          assert.equal "WTF$$%@! SERVER VOMIT", response.body
          done()
        gh.get "/foo", never_called

      it "passes error if request failed", (done) ->
        kablooie()
        gh.handleErrors (response) ->
          assert.ok /kablooie/i.exec response.error
          done()
        gh.get "/foo", never_called

      it "still logs errors", (done) ->
        network.reply(406, message: "I hate you!")
        expected = 2
        cb = ->
          expected -= 1
          done() if expected is 0
        mock_robot.onError = cb
        gh.handleErrors cb
        gh.get "/foo", never_called

      it "works in combination with withOptions", (done) ->
        network.matchHeader('Accept', 'application/vnd.github.special+json')
        network.reply(406, message: "I hate you!")
        gh.handleErrors (response) ->
          assert.equal 406, response.statusCode
          done()
        gh.withOptions(apiVersion: 'special').get "/foo", never_called

      it "can be passed as withOptions", (done) ->
        network.reply(406, message: "I hate you!")
        errHandler = (response) ->
          assert.equal 406, response.statusCode
          done()
        gh.withOptions(errorHandler: errHandler).get "/foo", never_called

    describe "without robot given", ->
      before ->
        gh = require("../src/githubot")
      it "complains to stderr", (done) ->
        console._old_error = console.error
        console.error = (msg) ->
          if msg.match /bad credentials/i
            console.error = @_old_error
            done()
          else
            @_old_error.call process.stderr, msg
        network.reply(401, message: "Bad credentials!")
        gh.get "/foo", never_called


<|start_filename|>test/acceptance/external.coffee<|end_filename|>
# This test does actual API requests against github.
# All others mock out the network activity.
[ gh, assert ] = require "../test_helper"

describe "actual request", ->
  response = [ { name: "foo", commit: { sha: "abcdeg", url: "xxx" } } ]
  success = (done) ->
    (body) ->
      done()
  it "works", (done) ->
    gh.branches "iangreenleaf/githubot", (branches) ->
      assert.ok branches[0].name
      assert.ok branches[0].commit.sha
      assert.ok branches[0].commit.url
      done()
