ChildProcess = require 'child_process'
path = require 'path'
fs = require 'fs-plus'
BufferedProcess  = require '../src/buffered-process'

describe "BufferedProcess", ->
  describe "when a bad command is specified", ->
    [oldOnError] = []
    beforeEach ->
      oldOnError = window.onerror
      window.onerror = jasmine.createSpy()

    afterEach ->
      window.onerror = oldOnError

    describe "when there is an error handler specified", ->
      describe "when an error event is emitted by the process", ->
        it "calls the error handler and does not throw an exception", ->
          bufferedProcess = new BufferedProcess
            command: 'bad-command-nope1'
            args: ['nothing']
            options: {shell: false}

          errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
          bufferedProcess.onWillThrowError(errorSpy)

          waitsFor -> errorSpy.callCount > 0

          runs ->
            expect(window.onerror).not.toHaveBeenCalled()
            expect(errorSpy).toHaveBeenCalled()
            expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope1 ENOENT'

      describe "when an error is thrown spawning the process", ->
        it "calls the error handler and does not throw an exception", ->
          spyOn(ChildProcess, 'spawn').andCallFake ->
            error = new Error('Something is really wrong')
            error.code = 'EAGAIN'
            throw error

          bufferedProcess = new BufferedProcess
            command: 'ls'
            args: []
            options: {}

          errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
          bufferedProcess.onWillThrowError(errorSpy)

          waitsFor -> errorSpy.callCount > 0

          runs ->
            expect(window.onerror).not.toHaveBeenCalled()
            expect(errorSpy).toHaveBeenCalled()
            expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'Something is really wrong'

    describe "when there is not an error handler specified", ->
      it "does throw an exception", ->
        new BufferedProcess
          command: 'bad-command-nope2'
          args: ['nothing']
          options: {shell: false}

        waitsFor -> window.onerror.callCount > 0

        runs ->
          expect(window.onerror).toHaveBeenCalled()
          expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope2`'
          expect(window.onerror.mostRecentCall.args[4].name).toBe 'BufferedProcessError'

  describe "when autoStart is false", ->
    it "doesnt start unless start method is called", ->
      stdout = ''
      stderr = ''
      exitCallback = jasmine.createSpy('exit callback')
      apmProcess = new BufferedProcess
        autoStart: false
        command: atom.packages.getApmPath()
        args: ['-h']
        options: {}
        stdout: (lines) -> stdout += lines
        stderr: (lines) -> stderr += lines
        exit: exitCallback

      expect(apmProcess.started).not.toBe(true)
      apmProcess.start()
      expect(apmProcess.started).toBe(true)

      waitsFor -> exitCallback.callCount is 1
      runs ->
        expect(stderr).toContain 'apm - Atom Package Manager'
        expect(stdout).toEqual ''

  it "calls the specified stdout, stderr, and exit callbacks", ->
    stdout = ''
    stderr = ''
    exitCallback = jasmine.createSpy('exit callback')
    new BufferedProcess
      command: atom.packages.getApmPath()
      args: ['-h']
      options: {}
      stdout: (lines) -> stdout += lines
      stderr: (lines) -> stderr += lines
      exit: exitCallback

    waitsFor -> exitCallback.callCount is 1

    runs ->
      expect(stderr).toContain 'apm - Atom Package Manager'
      expect(stdout).toEqual ''

  it "calls the specified stdout callback with whole lines", ->
    exitCallback = jasmine.createSpy('exit callback')
    loremPath = require.resolve("./fixtures/lorem.txt")
    content = fs.readFileSync(loremPath).toString()
    baseContent = content.split('\n')
    stdout = ''
    allLinesEndWithNewline = true
    new BufferedProcess
      command: if process.platform is 'win32' then 'type' else 'cat'
      args: [loremPath]
      options: {}
      stdout: (lines) ->
        endsWithNewline = (lines.charAt lines.length - 1) is '\n'
        if not endsWithNewline then allLinesEndWithNewline = false
        stdout += lines
      exit: exitCallback

    waitsFor -> exitCallback.callCount is 1

    runs ->
      expect(allLinesEndWithNewline).toBeTrue
      expect(stdout).toBe content

  describe "on Windows", ->
    originalPlatform = null

    beforeEach ->
      # Prevent any commands from actually running and affecting the host
      originalSpawn = ChildProcess.spawn
      spyOn(ChildProcess, 'spawn')
      originalPlatform = process.platform
      Object.defineProperty process, 'platform', value: 'win32'

    afterEach ->
      Object.defineProperty process, 'platform', value: originalPlatform

    describe "when the explorer command is spawned on Windows", ->
      it "doesn't quote arguments of the form /root,C...", ->
        new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
        expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"explorer.exe /root,C:\\foo"'

    it "spawns the command using a cmd.exe wrapper when options.shell is undefined", ->
      new BufferedProcess({command: 'dir'})
      expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
      expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
      expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/d'
      expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '/c'
      expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"dir"'
