import * as core from '@actions/core'
import {RetryHelper} from '../src/retry-helper'

let info: string[]
let retryHelper: RetryHelper

describe('retry-helper tests', () => {
  beforeAll(() => {
    // Mock @actions/core info()
    jest.spyOn(core, 'info').mockImplementation((message: string) => {
      info.push(message)
    })

    retryHelper = new RetryHelper(3, 0, 0)
  })

  beforeEach(() => {
    // Reset info
    info = []
  })

  afterAll(() => {
    // Restore
    jest.restoreAllMocks()
  })

  it('first attempt succeeds', async () => {
    const actual = await retryHelper.execute(async () => {
      return 'some result'
    })
    expect(actual).toBe('some result')
    expect(info).toHaveLength(0)
  })

  it('second attempt succeeds', async () => {
    let attempts = 0
    const actual = await retryHelper.execute(async () => {
      if (++attempts === 1) {
        throw new Error('some error')
      }

      return Promise.resolve('some result')
    })
    expect(attempts).toBe(2)
    expect(actual).toBe('some result')
    expect(info).toHaveLength(2)
    expect(info[0]).toBe('some error')
    expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
  })

  it('third attempt succeeds', async () => {
    let attempts = 0
    const actual = await retryHelper.execute(async () => {
      if (++attempts < 3) {
        throw new Error(`some error ${attempts}`)
      }

      return Promise.resolve('some result')
    })
    expect(attempts).toBe(3)
    expect(actual).toBe('some result')
    expect(info).toHaveLength(4)
    expect(info[0]).toBe('some error 1')
    expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
    expect(info[2]).toBe('some error 2')
    expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
  })

  it('all attempts fail', async () => {
    let attempts = 0
    let error: Error = null as unknown as Error
    try {
      await retryHelper.execute(() => {
        throw new Error(`some error ${++attempts}`)
      })
    } catch (err) {
      error = err
    }
    expect(error.message).toBe('some error 3')
    expect(attempts).toBe(3)
    expect(info).toHaveLength(4)
    expect(info[0]).toBe('some error 1')
    expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
    expect(info[2]).toBe('some error 2')
    expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
  })

  it('checks retryable after first attempt', async () => {
    let attempts = 0
    let error: Error = null as unknown as Error
    try {
      await retryHelper.execute(
        async () => {
          throw new Error(`some error ${++attempts}`)
        },
        () => false
      )
    } catch (err) {
      error = err
    }
    expect(error.message).toBe('some error 1')
    expect(attempts).toBe(1)
    expect(info).toHaveLength(0)
  })

  it('checks retryable after second attempt', async () => {
    let attempts = 0
    let error: Error = null as unknown as Error
    try {
      await retryHelper.execute(
        async () => {
          throw new Error(`some error ${++attempts}`)
        },
        (e: Error) => e.message === 'some error 1'
      )
    } catch (err) {
      error = err
    }
    expect(error.message).toBe('some error 2')
    expect(attempts).toBe(2)
    expect(info).toHaveLength(2)
    expect(info[0]).toBe('some error 1')
    expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
  })
})
