import * as tls from 'tls'
import { c, client, integrationSetup, s, server } from './IntegrationCommon'

integrationSetup()

describe('2.1.  Advertising the AUTHINFO Extension', () => {
  test(`Example of AUTHINFO capabilities before and after the use of the
   STARTTLS [NNTP-TLS] extension:`, async () => {
    c('CAPABILITIES')
    s('101 Capability list:')
    s('VERSION 2')
    s('READER')
    s('IHAVE')
    s('STARTTLS')
    s('AUTHINFO SASL')
    s('SASL CRAM-MD5 DIGEST-MD5 GSSAPI')
    s('LIST ACTIVE NEWSGROUPS')
    s('.')

    let response = await client.capabilities()
    expect(response).toEqual({
      code: 101,
      comment: 'Capability list:',
      description: 'Capability list follows (multi-line)',
      capabilities: {
        VERSION: ['2'],
        READER: [],
        IHAVE: [],
        STARTTLS: [],
        AUTHINFO: ['SASL'],
        SASL: ['CRAM-MD5', 'DIGEST-MD5', 'GSSAPI'],
        LIST: ['ACTIVE', 'NEWSGROUPS']
      }
    })

    c('STARTTLS')
    s('382 Continue with TLS negotiation', server.upgradeTls)
    // [TLS negotiation proceeds, further commands protected by TLS]

    response = await client.startTls()
    expect(response).toEqual({
      code: 382,
      comment: 'Continue with TLS negotiation',
      description: 'Continue with TLS negotiation',
      socket: expect.any(tls.TLSSocket)
    })

    c('CAPABILITIES')
    s('101 Capability list:')
    s('VERSION 2')
    s('READER')
    s('IHAVE')
    s('AUTHINFO USER SASL')
    s('SASL CRAM-MD5 DIGEST-MD5 GSSAPI PLAIN EXTERNAL')
    s('LIST ACTIVE NEWSGROUPS')
    s('.')

    response = await client.capabilities()
    expect(response).toEqual({
      code: 101,
      comment: 'Capability list:',
      description: 'Capability list follows (multi-line)',
      capabilities: {
        VERSION: ['2'],
        READER: [],
        IHAVE: [],
        AUTHINFO: ['USER', 'SASL'],
        SASL: ['CRAM-MD5', 'DIGEST-MD5', 'GSSAPI', 'PLAIN', 'EXTERNAL'],
        LIST: ['ACTIVE', 'NEWSGROUPS']
      }
    })
  })
})

describe('2.3.  AUTHINFO USER/PASS Command', () => {
  test('Example of successful AUTHINFO USER:', async () => {
    c('AUTHINFO USER wilma')
    s('281 Authentication accepted')

    const response = await client.authInfoUser('wilma')
    expect(response).toEqual({
      code: 281,
      comment: 'Authentication accepted',
      description: 'Authentication accepted'
    })
  })

  test('Example of successful AUTHINFO USER/PASS:', async () => {
    c('AUTHINFO USER fred')
    s('381 Enter passphrase')

    let response = await client.authInfoUser('fred')
    expect(response).toEqual({
      code: 381,
      comment: 'Enter passphrase',
      description: 'Password required',
      authInfoPass: expect.any(Function)
    })

    c('AUTHINFO PASS flintstone')
    s('281 Authentication accepted')

    response = await response.authInfoPass('flintstone')
    expect(response).toEqual({
      code: 281,
      comment: 'Authentication accepted',
      description: 'Authentication accepted'
    })
  })

  test('Example of AUTHINFO USER/PASS requiring a security layer:', async () => {
    c('AUTHINFO USER fred@stonecanyon.example.com')
    s('483 Encryption or stronger authentication required')

    let caught = false
    return client
      .authInfoUser('fred@stonecanyon.example.com')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 483,
          comment: 'Encryption or stronger authentication required',
          description: 'command unavailable until suitable privacy has been arranged'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test('Example of failed AUTHINFO USER/PASS:', async () => {
    c('AUTHINFO USER barney')
    s('381 Enter passphrase')

    const response = await client.authInfoUser('barney')
    expect(response).toEqual({
      code: 381,
      comment: 'Enter passphrase',
      description: 'Password required',
      authInfoPass: expect.any(Function)
    })

    c('AUTHINFO PASS flintstone')
    s('481 Authentication failed')

    let caught = false
    return response
      .authInfoPass('flintstone')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 481,
          comment: 'Authentication failed',
          description: 'Authentication failed/rejected'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test('Example of AUTHINFO PASS before AUTHINFO USER:', async () => {
    c('AUTHINFO PASS flintstone')
    s('482 Authentication commands issued out of sequence')

    let caught = false
    return client
      .command('AUTHINFO PASS', 'flintstone')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 482,
          comment: 'Authentication commands issued out of sequence',
          description: 'Authentication commands issued out of sequence'
        })
      })
      .then(() => expect(caught).toBe(true))
  })
})

describe('2.4.  AUTHINFO SASL Command', () => {
  test(`Example of the [PLAIN] SASL mechanism under a TLS layer, using an
   initial client response:`, async () => {
    c('CAPABILITIES')
    s('101 Capability list:')
    s('VERSION 2')
    s('READER')
    s('STARTTLS')
    s('AUTHINFO SASL')
    s('SASL CRAM-MD5 DIGEST-MD5 GSSAPI')
    s('LIST ACTIVE NEWSGROUPS')
    s('.')

    let response = await client.capabilities()
    expect(response).toEqual({
      code: 101,
      comment: 'Capability list:',
      description: 'Capability list follows (multi-line)',
      capabilities: {
        VERSION: ['2'],
        READER: [],
        STARTTLS: [],
        AUTHINFO: ['SASL'],
        SASL: ['CRAM-MD5', 'DIGEST-MD5', 'GSSAPI'],
        LIST: ['ACTIVE', 'NEWSGROUPS']
      }
    })

    c('STARTTLS')
    s('382 Continue with TLS negotiation', server.upgradeTls)
    // [TLS negotiation proceeds, further commands protected by TLS]

    response = await client.startTls()
    expect(response).toEqual({
      code: 382,
      comment: 'Continue with TLS negotiation',
      description: 'Continue with TLS negotiation',
      socket: expect.any(tls.TLSSocket)
    })

    c('CAPABILITIES')
    s('101 Capability list:')
    s('VERSION 2')
    s('READER')
    s('AUTHINFO USER SASL')
    s('SASL CRAM-MD5 DIGEST-MD5 GSSAPI PLAIN EXTERNAL')
    s('LIST ACTIVE NEWSGROUPS')
    s('.')

    response = await client.capabilities()
    expect(response).toEqual({
      code: 101,
      comment: 'Capability list:',
      description: 'Capability list follows (multi-line)',
      capabilities: {
        VERSION: ['2'],
        READER: [],
        AUTHINFO: ['USER', 'SASL'],
        SASL: ['CRAM-MD5', 'DIGEST-MD5', 'GSSAPI', 'PLAIN', 'EXTERNAL'],
        LIST: ['ACTIVE', 'NEWSGROUPS']
      }
    })

    c('AUTHINFO SASL PLAIN AHRlc3QAMTIzNA==')
    s('281 Authentication accepted')

    response = await client.authInfoSaslPlain(undefined, 'test', '1234')
    expect(response).toEqual({
      code: 281,
      description: 'Authentication accepted',
      comment: 'Authentication accepted'
    })
  })

  test(`Example of the EXTERNAL SASL mechanism under a TLS layer, using the
   authorization identity derived from the client TLS certificate, and
   thus a zero-length initial client response (commands prior to
   AUTHINFO SASL are the same as the previous example and have been
   omitted):`, async () => {
    c('AUTHINFO SASL EXTERNAL =')
    s('281 Authentication accepted')

    const response = await client.authInfoSasl('EXTERNAL', '=')
    expect(response).toEqual({
      code: 281,
      comment: 'Authentication accepted',
      description: 'Authentication accepted'
    })
  })

  test(`Example of the [DIGEST-MD5] SASL mechanism, which includes a server
   challenge and server success data (white space has been inserted for
   clarity; base64-encoded data is actually sent as a single line with
   no embedded white space):`, async () => {
    c('AUTHINFO SASL DIGEST-MD5')
    s(
      '383 bm9uY2U9InNheUFPaENFS0dJZFBNSEMwd3RsZUxxT0ljT0kyd1FZSWU0' +
        'enplQXR1aVE9IixyZWFsbT0iZWFnbGUub2NlYW5hLmNvbSIscW9wPSJhdXRo' +
        'LGF1dGgtaW50LGF1dGgtY29uZiIsY2lwaGVyPSJyYzQtNDAscmM0LTU2LHJj' +
        'NCxkZXMsM2RlcyIsbWF4YnVmPTQwOTYsY2hhcnNldD11dGYtOCxhbGdvcml0' +
        'aG09bWQ1LXNlc3M='
    )

    let response = await client.authInfoSasl('DIGEST-MD5')
    expect(response).toEqual({
      code: 383,
      comment: '',
      description: 'Continue with SASL exchange',
      cancel: expect.any(Function),
      continue: expect.any(Function),
      challenge:
        'bm9uY2U9InNheUFPaENFS0dJZFBNSEMwd3RsZUxxT0ljT0kyd1FZSWU0' +
        'enplQXR1aVE9IixyZWFsbT0iZWFnbGUub2NlYW5hLmNvbSIscW9wPSJhdXRo' +
        'LGF1dGgtaW50LGF1dGgtY29uZiIsY2lwaGVyPSJyYzQtNDAscmM0LTU2LHJj' +
        'NCxkZXMsM2RlcyIsbWF4YnVmPTQwOTYsY2hhcnNldD11dGYtOCxhbGdvcml0' +
        'aG09bWQ1LXNlc3M='
    })

    c(
      'dXNlcm5hbWU9InRlc3QiLHJlYWxtPSJlYWdsZS5vY2VhbmEuY29tIixub25j' +
        'ZT0ic2F5QU9oQ0VLR0lkUE1IQzB3dGxlTHFPSWNPSTJ3UVlJZTR6emVBdHVp' +
        'UT0iLGNub25jZT0iMFkzSlFWMlRnOVNjRGlwK08xU1ZDMHJoVmcvLytkbk9J' +
        'aUd6LzdDZU5KOD0iLG5jPTAwMDAwMDAxLHFvcD1hdXRoLWNvbmYsY2lwaGVy' +
        'PXJjNCxtYXhidWY9MTAyNCxkaWdlc3QtdXJpPSJubnRwL2xvY2FsaG9zdCIs' +
        'cmVzcG9uc2U9ZDQzY2Y2NmNmZmE5MDNmOWViMDM1NmMwOGEzZGIwZjI='
    )
    s('283 cnNwYXV0aD1kZTJlMTI3ZTVhODFjZGE1M2Q5N2FjZGEzNWNkZTgzYQ==')

    response = await response.continue(
      'dXNlcm5hbWU9InRlc3QiLHJlYWxtPSJlYWdsZS5vY2VhbmEuY29tIixub25j' +
        'ZT0ic2F5QU9oQ0VLR0lkUE1IQzB3dGxlTHFPSWNPSTJ3UVlJZTR6emVBdHVp' +
        'UT0iLGNub25jZT0iMFkzSlFWMlRnOVNjRGlwK08xU1ZDMHJoVmcvLytkbk9J' +
        'aUd6LzdDZU5KOD0iLG5jPTAwMDAwMDAxLHFvcD1hdXRoLWNvbmYsY2lwaGVy' +
        'PXJjNCxtYXhidWY9MTAyNCxkaWdlc3QtdXJpPSJubnRwL2xvY2FsaG9zdCIs' +
        'cmVzcG9uc2U9ZDQzY2Y2NmNmZmE5MDNmOWViMDM1NmMwOGEzZGIwZjI='
    )
    expect(response).toEqual({
      code: 283,
      comment: '',
      description: 'Authentication accepted (with success data)',
      challenge: 'cnNwYXV0aD1kZTJlMTI3ZTVhODFjZGE1M2Q5N2FjZGEzNWNkZTgzYQ=='
    })
  })

  test(`Example of a failed authentication due to bad [GSSAPI] credentials.
   Note that although the mechanism can utilize the initial response,
   the client chooses not to use it because of its length, resulting in
   a zero-length server challenge (here, white space has been inserted
   for clarity; base64-encoded data is actually sent as a single line
   with no embedded white space):`, async () => {
    c('AUTHINFO SASL GSSAPI')
    s('383 =')

    const response = await client.authInfoSasl('GSSAPI')
    expect(response).toEqual({
      code: 383,
      comment: '',
      description: 'Continue with SASL exchange',
      cancel: expect.any(Function),
      continue: expect.any(Function),
      challenge: '='
    })

    c(
      'YIICOAYJKoZIhvcSAQICAQBuggInMIICI6ADAgEFoQMCAQ6iBwMFACAAAACj' +
        'ggE/YYIBOzCCATegAwIBBaEYGxZURVNULk5FVC5JU0MuVVBFTk4uRURVoiQw' +
        'IqADAgEDoRswGRsEbmV3cxsRbmV0bmV3cy51cGVubi5lZHWjge8wgeygAwIB' +
        'EKEDAgECooHfBIHcSQfLKC8vm2i17EXmomwk6hHvjBY/BnKnvvDTrbno3198' +
        'vlX2RSUt+CjuAKhcDcj4DW0gvZEqH7t5v9yWedzztlpaThebBat6hQNr9NJP' +
        'ozh1/+74HUwhGWb50KtjuftO/ftQ8q0nTuYKgIq6PM4tp2ddo1IfpjfdNR9E' +
        '95GFi3y1uBT7lQOwtQbRJUjPSO3ijdue9V7cNNVmYsBsqNsaHhvlBJEXf4WJ' +
        'djH8yG+Dw/gX8fUTtC5fDpB5zLt01mkSXh6Wc4UhqQtwZBI2t/+TpX1okbg6' +
        'Hr1ZZupeH6SByjCBx6ADAgEQooG/BIG8GnCmcXWtqhXh48dGTLHQgJ04K5Fj' +
        'RMMq2qPSbiha9lq0osqR2KAnQA6LioWYxU+6yPKpBDSC5WOT441fUfkM8iAL' +
        'kW3uNc+luFCGcnDsacrmoVU7Y6Akcp9m7Fm7orRc+TWSWPpBg3OR2oG3ATW0' +
        '0NAz8TT06VOLVxIMUTINKdYVI/Ja7f3sy+/N4LGkJqScCQOwlo5tfDWn/UQF' +
        'iTWo5Zw435rH8pjy2smQCnqC14v3NMAWTu4j+dzHUNw='
    )
    s('481 Authentication error')

    let caught = false
    return response
      .continue(
        'YIICOAYJKoZIhvcSAQICAQBuggInMIICI6ADAgEFoQMCAQ6iBwMFACAAAACj' +
          'ggE/YYIBOzCCATegAwIBBaEYGxZURVNULk5FVC5JU0MuVVBFTk4uRURVoiQw' +
          'IqADAgEDoRswGRsEbmV3cxsRbmV0bmV3cy51cGVubi5lZHWjge8wgeygAwIB' +
          'EKEDAgECooHfBIHcSQfLKC8vm2i17EXmomwk6hHvjBY/BnKnvvDTrbno3198' +
          'vlX2RSUt+CjuAKhcDcj4DW0gvZEqH7t5v9yWedzztlpaThebBat6hQNr9NJP' +
          'ozh1/+74HUwhGWb50KtjuftO/ftQ8q0nTuYKgIq6PM4tp2ddo1IfpjfdNR9E' +
          '95GFi3y1uBT7lQOwtQbRJUjPSO3ijdue9V7cNNVmYsBsqNsaHhvlBJEXf4WJ' +
          'djH8yG+Dw/gX8fUTtC5fDpB5zLt01mkSXh6Wc4UhqQtwZBI2t/+TpX1okbg6' +
          'Hr1ZZupeH6SByjCBx6ADAgEQooG/BIG8GnCmcXWtqhXh48dGTLHQgJ04K5Fj' +
          'RMMq2qPSbiha9lq0osqR2KAnQA6LioWYxU+6yPKpBDSC5WOT441fUfkM8iAL' +
          'kW3uNc+luFCGcnDsacrmoVU7Y6Akcp9m7Fm7orRc+TWSWPpBg3OR2oG3ATW0' +
          '0NAz8TT06VOLVxIMUTINKdYVI/Ja7f3sy+/N4LGkJqScCQOwlo5tfDWn/UQF' +
          'iTWo5Zw435rH8pjy2smQCnqC14v3NMAWTu4j+dzHUNw='
      )
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 481,
          comment: 'Authentication error',
          description: 'Authentication failed/rejected'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test('Example of a client aborting in the midst of an exchange:', async () => {
    c('AUTHINFO SASL GSSAPI')
    s('383 =')

    const response = await client.authInfoSasl('GSSAPI')
    expect(response).toEqual({
      code: 383,
      comment: '',
      description: 'Continue with SASL exchange',
      cancel: expect.any(Function),
      continue: expect.any(Function),
      challenge: '='
    })

    c('*')
    s('481 Authentication aborted as requested')

    let caught = false
    return response
      .cancel()
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 481,
          comment: 'Authentication aborted as requested',
          description: 'Authentication failed/rejected'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test(`Example of attempting to use a mechanism that is not supported by the
   server:`, async () => {
    c('AUTHINFO SASL EXAMPLE')
    s('503 Mechanism not recognized')

    let caught = false
    return client
      .authInfoSasl('EXAMPLE')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 503,
          comment: 'Mechanism not recognized',
          description: 'feature not supported'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test(`Example of attempting to use a mechanism that requires a security
   layer:`, async () => {
    c('AUTHINFO SASL PLAIN')
    s('483 Encryption or stronger authentication required')

    let caught = false
    return client
      .authInfoSasl('PLAIN')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 483,
          comment: 'Encryption or stronger authentication required',
          description: 'command unavailable until suitable privacy has been arranged'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test(`Example of using an initial response with a mechanism that doesn't
   support it (the server must start the exchange when using
   [CRAM-MD5]):`, async () => {
    c('AUTHINFO SASL CRAM-MD5 AHRlc3QAMTIzNA==')
    s('482 SASL protocol error')

    let caught = false
    return client
      .authInfoSasl('CRAM-MD5', 'AHRlc3QAMTIzNA==')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 482,
          comment: 'SASL protocol error',
          description: 'SASL protocol error'
        })
      })
      .then(() => expect(caught).toBe(true))
  })

  test(`Example of an authentication that failed due to an incorrectly
   encoded response:`, async () => {
    c('AUTHINFO SASL CRAM-MD5')
    s('383 PDE1NDE2NzQ5My4zMjY4MzE3QHRlc3RAZXhhbXBsZS5jb20+')

    const response = await client.authInfoSasl('CRAM-MD5')
    expect(response).toEqual({
      code: 383,
      comment: '',
      description: 'Continue with SASL exchange',
      cancel: expect.any(Function),
      continue: expect.any(Function),
      challenge: 'PDE1NDE2NzQ5My4zMjY4MzE3QHRlc3RAZXhhbXBsZS5jb20+'
    })

    c('abcd=efg')
    s('504 Base64 encoding error')

    let caught = false
    return response
      .continue('abcd=efg')
      .catch(response => {
        caught = true
        expect(response).toEqual({
          code: 504,
          comment: 'Base64 encoding error',
          description: 'error in base64-encoding [RFC4648] of an argument'
        })
      })
      .then(() => expect(caught).toBe(true))
  })
})
