import { afterAll, beforeAll, describe, expect, test, vi } from 'vp/test'
import * as Base64 from '../../core/Base64.js'
import * as Bytes from '../../core/Bytes.js'
import * as Hash from '../../core/Hash.js'
import * as Hex from '../../core/Hex.js'
import * as P256 from '../../core/P256.js'
import * as Signature from '../../core/Signature.js'
import {
  Authentication,
  Authenticator,
  Registration,
  type Types,
} from '../index.js'

beforeAll(() => {
  vi.stubGlobal('window', {
    location: {
      hostname: 'https://example.com',
    },
    document: {
      title: 'My Website',
    },
  })
})
afterAll(() => {
  vi.restoreAllMocks()
})

describe('create', () => {
  test('default', async () => {
    let options: CredentialCreationOptions | undefined

    const credential = await Registration.create({
      createFn(options_) {
        options = options_
        return Promise.resolve({
          id: 'm1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs',
          response: {
            getPublicKey() {
              return [
                48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134,
                72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 171, 137, 20, 0, 20, 15, 196,
                248, 233, 65, 206, 15, 249, 14, 65, 157, 233, 71, 10, 202, 202,
                97, 59, 189, 113, 122, 71, 117, 67, 80, 49, 167, 216, 132, 49,
                142, 145, 159, 211, 179, 229, 166, 49, 216, 102, 216, 163, 128,
                180, 64, 99, 231, 15, 12, 56, 30, 225, 110, 6, 82, 247, 249,
                117, 84,
              ]
            },
          },
        } as any)
      },
      name: 'Foo',
    })

    expect(credential).toMatchInlineSnapshot(`
      {
        "attestationObject": undefined,
        "clientDataJSON": undefined,
        "id": "m1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs",
        "publicKey": {
          "prefix": 4,
          "x": "0xab891400140fc4f8e941ce0ff90e419de9470acaca613bbd717a4775435031a7",
          "y": "0xd884318e919fd3b3e5a631d866d8a380b44063e70f0c381ee16e0652f7f97554",
        },
        "raw": {
          "id": "m1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs",
          "response": {
            "getPublicKey": [Function],
          },
        },
      }
    `)
    expect(options).toMatchInlineSnapshot(`
      {
        "publicKey": {
          "attestation": "none",
          "authenticatorSelection": {
            "requireResidentKey": false,
            "residentKey": "preferred",
            "userVerification": "required",
          },
          "challenge": Uint8Array [
            105,
            171,
            180,
            181,
            160,
            222,
            75,
            198,
            42,
            42,
            32,
            31,
            141,
            37,
            186,
            233,
          ],
          "pubKeyCredParams": [
            {
              "alg": -7,
              "type": "public-key",
            },
          ],
          "rp": {
            "id": "https://example.com",
            "name": "My Website",
          },
          "user": {
            "displayName": "Foo",
            "id": Uint8Array [
              182,
              8,
              199,
              66,
              131,
              243,
              52,
              225,
              240,
              71,
              219,
              191,
              29,
              170,
              36,
              7,
              212,
              29,
              70,
              137,
              172,
              166,
              124,
              66,
              39,
              150,
              249,
              54,
              172,
              206,
              22,
              183,
            ],
            "name": "Foo",
          },
        },
      }
    `)
  })

  test('error: null credential', async () => {
    await expect(() =>
      Registration.create({
        createFn() {
          return Promise.resolve(null)
        },
        name: 'Foo',
      }),
    ).rejects.toMatchInlineSnapshot(`
      [Registration.CreateFailedError: Failed to create credential.

      Details: Failed to create credential.]
    `)
  })

  test('error: thrown', async () => {
    await expect(() =>
      Registration.create({
        createFn() {
          return Promise.reject(new Error('foo'))
        },
        name: 'Foo',
      }),
    ).rejects.toMatchInlineSnapshot(`
      [Registration.CreateFailedError: Failed to create credential.

      Details: foo]
    `)
  })

  test('behavior: firefox workaround', async () => {
    const credential = await Registration.create({
      createFn(_options) {
        return Promise.resolve({
          id: 'DxRcX5C6BRQ-q-CO7XEFwrnmKlk',
          response: {
            attestationObject: new Uint8Array([
              163, 99, 102, 109, 116, 100, 110, 111, 110, 101, 103, 97, 116,
              116, 83, 116, 109, 116, 160, 104, 97, 117, 116, 104, 68, 97, 116,
              97, 88, 152, 73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15,
              100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92,
              243, 186, 131, 29, 151, 99, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 15, 20, 92, 95, 144, 186, 5, 20,
              62, 171, 224, 142, 237, 113, 5, 194, 185, 230, 42, 89, 165, 1, 2,
              3, 38, 32, 1, 33, 88, 32, 98, 163, 23, 104, 212, 79, 94, 255, 34,
              47, 141, 112, 196, 203, 97, 171, 210, 132, 11, 39, 214, 23, 167,
              254, 141, 17, 183, 45, 213, 232, 111, 193, 34, 88, 32, 102, 17,
              186, 227, 241, 226, 205, 56, 228, 5, 21, 55, 118, 167, 220, 182,
              153, 91, 130, 84, 161, 65, 110, 173, 16, 42, 9, 108, 176, 216, 6,
              24,
            ]),
            getPublicKey() {
              throw new Error('Permission denied to access object')
            },
          },
        } as any)
      },
      name: 'Example',
    })

    const publicKey = credential.publicKey
    expect(publicKey).toMatchInlineSnapshot(`
      {
        "prefix": 4,
        "x": "0x62a31768d44f5eff222f8d70c4cb61abd2840b27d617a7fe8d11b72dd5e86fc1",
        "y": "0x6611bae3f1e2cd38e405153776a7dcb6995b8254a1416ead102a096cb0d80618",
      }
    `)

    expect(
      Authentication.verify({
        metadata: {
          authenticatorData:
            '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000',
          clientDataJSON:
            '{"type":"webauthn.get","challenge":"9jEFijuhEWrM4SOW-tChJbUEHEP44VcjcJ-Bqo1fTM8","origin":"http://localhost:5173"}',
          challengeIndex: 23,
          typeIndex: 1,
          userVerificationRequired: true,
        },
        challenge:
          '0xf631058a3ba1116acce12396fad0a125b5041c43f8e15723709f81aa8d5f4ccf',
        publicKey,
        signature: {
          r: '0xfb93e78ee89f6110313194b0e6ea55c8a33d59ec89a1eb009a179dcbebcd33f1',
          s: '0x0beb1ae5eaa0c2823343507fe836b35b755b6fea5973fbe58941eb7184e714ce',
        },
      }),
    ).toBeTruthy()
  })
})

describe('getOptions', () => {
  test('default', () => {
    expect(
      Registration.getOptions({
        name: 'Foo',
      }),
    ).toMatchInlineSnapshot(`
      {
        "publicKey": {
          "attestation": "none",
          "authenticatorSelection": {
            "requireResidentKey": false,
            "residentKey": "preferred",
            "userVerification": "required",
          },
          "challenge": Uint8Array [
            105,
            171,
            180,
            181,
            160,
            222,
            75,
            198,
            42,
            42,
            32,
            31,
            141,
            37,
            186,
            233,
          ],
          "pubKeyCredParams": [
            {
              "alg": -7,
              "type": "public-key",
            },
          ],
          "rp": {
            "id": "https://example.com",
            "name": "My Website",
          },
          "user": {
            "displayName": "Foo",
            "id": Uint8Array [
              182,
              8,
              199,
              66,
              131,
              243,
              52,
              225,
              240,
              71,
              219,
              191,
              29,
              170,
              36,
              7,
              212,
              29,
              70,
              137,
              172,
              166,
              124,
              66,
              39,
              150,
              249,
              54,
              172,
              206,
              22,
              183,
            ],
            "name": "Foo",
          },
        },
      }
    `)
  })

  test('args: excludeCredentialIds', () => {
    expect(
      Registration.getOptions({
        excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
        name: 'Foo',
      }),
    ).toMatchInlineSnapshot(`
      {
        "publicKey": {
          "attestation": "none",
          "authenticatorSelection": {
            "requireResidentKey": false,
            "residentKey": "preferred",
            "userVerification": "required",
          },
          "challenge": Uint8Array [
            105,
            171,
            180,
            181,
            160,
            222,
            75,
            198,
            42,
            42,
            32,
            31,
            141,
            37,
            186,
            233,
          ],
          "excludeCredentials": [
            {
              "id": Uint8Array [
                167,
                58,
                80,
                101,
                24,
                87,
                82,
                70,
                232,
                143,
                230,
                255,
                178,
                177,
                244,
                95,
                141,
                151,
                37,
                46,
                192,
                139,
                102,
                87,
                119,
                175,
                189,
                150,
                113,
                84,
                46,
                40,
              ],
              "type": "public-key",
            },
          ],
          "pubKeyCredParams": [
            {
              "alg": -7,
              "type": "public-key",
            },
          ],
          "rp": {
            "id": "https://example.com",
            "name": "My Website",
          },
          "user": {
            "displayName": "Foo",
            "id": Uint8Array [
              182,
              8,
              199,
              66,
              131,
              243,
              52,
              225,
              240,
              71,
              219,
              191,
              29,
              170,
              36,
              7,
              212,
              29,
              70,
              137,
              172,
              166,
              124,
              66,
              39,
              150,
              249,
              54,
              172,
              206,
              22,
              183,
            ],
            "name": "Foo",
          },
        },
      }
    `)
  })

  test('args: user', () => {
    expect(
      Registration.getOptions({
        user: {
          name: 'Foo',
        },
      }),
    ).toMatchInlineSnapshot(`
      {
        "publicKey": {
          "attestation": "none",
          "authenticatorSelection": {
            "requireResidentKey": false,
            "residentKey": "preferred",
            "userVerification": "required",
          },
          "challenge": Uint8Array [
            105,
            171,
            180,
            181,
            160,
            222,
            75,
            198,
            42,
            42,
            32,
            31,
            141,
            37,
            186,
            233,
          ],
          "pubKeyCredParams": [
            {
              "alg": -7,
              "type": "public-key",
            },
          ],
          "rp": {
            "id": "https://example.com",
            "name": "My Website",
          },
          "user": {
            "displayName": "Foo",
            "id": Uint8Array [
              182,
              8,
              199,
              66,
              131,
              243,
              52,
              225,
              240,
              71,
              219,
              191,
              29,
              170,
              36,
              7,
              212,
              29,
              70,
              137,
              172,
              166,
              124,
              66,
              39,
              150,
              249,
              54,
              172,
              206,
              22,
              183,
            ],
            "name": "Foo",
          },
        },
      }
    `)
  })
})

describe('serializeOptions', () => {
  test('default', () => {
    const options = Registration.getOptions({ name: 'Foo' })
    const serialized = Registration.serializeOptions(options)

    expect(typeof serialized.publicKey!.challenge).toBe('string')
    expect(typeof serialized.publicKey!.user.id).toBe('string')
    expect(serialized).toMatchInlineSnapshot(`
      {
        "publicKey": {
          "attestation": "none",
          "authenticatorSelection": {
            "requireResidentKey": false,
            "residentKey": "preferred",
            "userVerification": "required",
          },
          "challenge": "0x69abb4b5a0de4bc62a2a201f8d25bae9",
          "pubKeyCredParams": [
            {
              "alg": -7,
              "type": "public-key",
            },
          ],
          "rp": {
            "id": "https://example.com",
            "name": "My Website",
          },
          "user": {
            "displayName": "Foo",
            "id": "tgjHQoPzNOHwR9u_HaokB9QdRomspnxCJ5b5NqzOFrc",
            "name": "Foo",
          },
        },
      }
    `)
    expect(() => JSON.stringify(serialized)).not.toThrow()
  })

  test('with excludeCredentials', () => {
    const options = Registration.getOptions({
      name: 'Foo',
      excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
    })
    const serialized = Registration.serializeOptions(options)

    expect(typeof serialized.publicKey!.excludeCredentials![0]!.id).toBe(
      'string',
    )
    expect(() => JSON.stringify(serialized)).not.toThrow()
  })

  test('with extensions (prf)', () => {
    const options: Types.CredentialCreationOptions = {
      publicKey: {
        challenge: new Uint8Array([1, 2, 3]),
        pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
        rp: { id: 'test', name: 'Test' },
        user: {
          id: new Uint8Array([4, 5, 6]),
          name: 'Test',
          displayName: 'Test',
        },
        extensions: {
          prf: {
            eval: {
              first: new Uint8Array([7, 8, 9]),
            },
          },
        },
      },
    }
    const serialized = Registration.serializeOptions(options)

    expect(typeof serialized.publicKey!.extensions!.prf!.eval.first).toBe(
      'string',
    )
    expect(() => JSON.stringify(serialized)).not.toThrow()
  })

  test('empty publicKey', () => {
    const serialized = Registration.serializeOptions({})
    expect(serialized).toEqual({})
  })
})

describe('deserializeOptions', () => {
  test('default', () => {
    const options = Registration.getOptions({ name: 'Foo' })
    const serialized = Registration.serializeOptions(options)
    const deserialized = Registration.deserializeOptions(serialized)

    expect(deserialized.publicKey!.challenge).toBeInstanceOf(Uint8Array)
    expect(deserialized.publicKey!.user.id).toBeInstanceOf(Uint8Array)
    expect(deserialized.publicKey!.challenge).toEqual(
      options.publicKey!.challenge,
    )
    expect(deserialized.publicKey!.user.id).toEqual(options.publicKey!.user.id)
    expect(deserialized.publicKey!.user.name).toBe(options.publicKey!.user.name)
    expect(deserialized.publicKey!.rp).toEqual(options.publicKey!.rp)
  })

  test('with excludeCredentials', () => {
    const options = Registration.getOptions({
      name: 'Foo',
      excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
    })
    const serialized = Registration.serializeOptions(options)
    const deserialized = Registration.deserializeOptions(serialized)

    expect(deserialized.publicKey!.excludeCredentials![0]!.id).toBeInstanceOf(
      Uint8Array,
    )
    expect(deserialized.publicKey!.excludeCredentials![0]!.id).toEqual(
      options.publicKey!.excludeCredentials![0]!.id,
    )
  })

  test('with extensions (prf)', () => {
    const options: Types.CredentialCreationOptions = {
      publicKey: {
        challenge: new Uint8Array([1, 2, 3]),
        pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
        rp: { id: 'test', name: 'Test' },
        user: {
          id: new Uint8Array([4, 5, 6]),
          name: 'Test',
          displayName: 'Test',
        },
        extensions: {
          prf: {
            eval: {
              first: new Uint8Array([7, 8, 9]),
            },
          },
        },
      },
    }
    const serialized = Registration.serializeOptions(options)
    const deserialized = Registration.deserializeOptions(serialized)

    expect(deserialized.publicKey!.extensions!.prf!.eval.first).toBeInstanceOf(
      Uint8Array,
    )
    expect(deserialized.publicKey!.extensions!.prf!.eval.first).toEqual(
      new Uint8Array([7, 8, 9]),
    )
  })

  test('empty publicKey', () => {
    const deserialized = Registration.deserializeOptions({})
    expect(deserialized).toEqual({})
  })

  test('JSON round-trip', () => {
    const options = Registration.getOptions({
      name: 'Foo',
      excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
    })
    const serialized = Registration.serializeOptions(options)
    const json = JSON.stringify(serialized)
    const parsed = JSON.parse(json)
    const deserialized = Registration.deserializeOptions(parsed)

    expect(deserialized.publicKey!.challenge).toEqual(
      options.publicKey!.challenge,
    )
    expect(deserialized.publicKey!.user.id).toEqual(options.publicKey!.user.id)
  })
})

describe('verify', () => {
  async function mockCreateCredential(options?: {
    challenge?: `0x${string}`
    origin?: string
    rpId?: string
    flag?: number
    fmt?: 'none' | 'packed'
  }) {
    const challenge = options?.challenge ?? '0xdeadbeef'
    const origin = options?.origin ?? 'https://example.com'
    const rpId = options?.rpId ?? 'example.com'
    const flag = options?.flag ?? 0x45 // UP + UV + AT
    const fmt = options?.fmt ?? 'packed'

    const { privateKey, publicKey } = P256.createKeyPair()
    const credentialId = new Uint8Array(20).fill(42)

    const authData = Authenticator.getAuthenticatorData({
      rpId,
      flag,
      credential: { id: credentialId, publicKey },
    })

    const clientDataJSON = Bytes.fromString(
      JSON.stringify({
        type: 'webauthn.create',
        challenge: Base64.fromHex(challenge, { url: true, pad: false }),
        origin,
        crossOrigin: false,
      }),
    )

    const attStmt: Record<string, unknown> = {}
    if (fmt === 'packed') {
      const clientDataHash = Hash.sha256(clientDataJSON, { as: 'Bytes' })
      const verificationData = Bytes.concat(
        Hex.toBytes(authData),
        clientDataHash,
      )
      const sig = P256.sign({
        payload: verificationData,
        privateKey,
        hash: true,
      })
      attStmt.alg = -7
      attStmt.sig = Signature.toDerBytes(sig)
    }

    const attestationObject = Hex.toBytes(
      Authenticator.getAttestationObject({ authData, fmt, attStmt }),
    )

    const credential = await Registration.create({
      createFn: () =>
        Promise.resolve({
          id: Base64.fromBytes(credentialId, { url: true, pad: false }),
          type: 'public-key',
          authenticatorAttachment: 'platform',
          rawId: credentialId.buffer,
          response: {
            clientDataJSON: clientDataJSON.buffer,
            attestationObject: attestationObject.buffer,
            getPublicKey() {
              throw new Error('Permission denied to access object')
            },
          },
          getClientExtensionResults: () => ({}),
        } as any),
      name: 'Example',
      challenge,
      rp: { id: rpId, name: 'Test' },
    })

    return { challenge, credential, origin, rpId }
  }

  test('default', async () => {
    const { challenge, credential, origin, rpId } = await mockCreateCredential()

    const result = Registration.verify({
      credential,
      challenge,
      origin,
      rpId,
    })

    expect(result.credential).toEqual(credential)
    expect(result.counter).toBe(0)
    expect(result.userVerified).toBe(true)
  })

  test('options: challenge as Uint8Array', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential()

    const result = Registration.verify({
      credential,
      challenge: Hex.toBytes(challenge),
      origin,
      rpId,
    })

    expect(result.credential.id).toBe(credential.id)
  })

  test('options: challenge as function', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential()

    const result = Registration.verify({
      credential,
      challenge: (c) =>
        c ===
        Base64.fromBytes(Hex.toBytes(challenge), { url: true, pad: false }),
      origin,
      rpId,
    })

    expect(result.credential.id).toBe(credential.id)
  })

  test('options: origin as array', async () => {
    const { credential, challenge, rpId } = await mockCreateCredential()

    const result = Registration.verify({
      credential,
      challenge,
      origin: ['https://other.com', 'https://example.com'],
      rpId,
    })

    expect(result.credential.id).toBe(credential.id)
  })

  test('options: userVerification = discouraged', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      flag: 0x41,
    }) // UP + AT, no UV

    const result = Registration.verify({
      credential,
      challenge,
      origin,
      rpId,
      userVerification: 'discouraged',
    })

    expect(result.credential.id).toBe(credential.id)
    expect(result.userVerified).toBeUndefined()
  })

  test('error: wrong type', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential()

    const tamperedClientDataJSON = JSON.stringify({
      type: 'webauthn.get',
      challenge: Base64.fromHex(challenge, { url: true, pad: false }),
      origin,
    })
    credential.clientDataJSON = new TextEncoder().encode(tamperedClientDataJSON)
      .buffer as ArrayBuffer

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin,
        rpId,
      }),
    ).toThrow('Expected clientData.type "webauthn.create"')
  })

  test('error: challenge mismatch', async () => {
    const { credential, origin, rpId } = await mockCreateCredential()

    expect(() =>
      Registration.verify({
        credential,
        challenge: '0xbadbadbadbad',
        origin,
        rpId,
      }),
    ).toThrow('Challenge mismatch')
  })

  test('error: origin mismatch', async () => {
    const { credential, challenge, rpId } = await mockCreateCredential()

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin: 'https://evil.com',
        rpId,
      }),
    ).toThrow('Origin mismatch')
  })

  test('error: rpId mismatch', async () => {
    const { credential, challenge, origin } = await mockCreateCredential()

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin,
        rpId: 'evil.com',
      }),
    ).toThrow('rpId hash mismatch')
  })

  test('error: user verification required but not set', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      flag: 0x41,
    }) // UP + AT, no UV

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin,
        rpId,
      }),
    ).toThrow('User verification flag not set')
  })

  test('options: attestation = none', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      fmt: 'none',
    })

    const result = Registration.verify({
      credential,
      challenge,
      origin,
      rpId,
      attestation: 'none',
    })

    expect(result.credential.id).toBe(credential.id)
  })

  test('error: BS flag set without BE flag', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      flag: 0x55,
    }) // UP + UV + AT + BS (without BE)

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin,
        rpId,
        attestation: 'none',
      }),
    ).toThrow('Backup state (BS) flag is set but backup eligibility (BE)')
  })

  test('error: credential ID mismatch', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential()

    // Tamper with the credential ID
    credential.id = 'tampered-id-that-does-not-match'

    expect(() =>
      Registration.verify({
        credential,
        challenge,
        origin,
        rpId,
      }),
    ).toThrow('Credential ID mismatch')
  })

  test('default attestation is "none" (matches getOptions default)', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      fmt: 'none',
    })

    // No `attestation` option; runtime default must be 'none' so that
    // unattested ('fmt: "none"') credentials are accepted by default.
    const result = Registration.verify({
      credential,
      challenge,
      origin,
      rpId,
    })

    expect(result.credential.id).toBe(credential.id)
  })

  test('error: fmt none rejected when attestation is required', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential({
      fmt: 'none',
    })

    expect(() =>
      Registration.verify({
        attestation: 'required',
        credential,
        challenge,
        origin,
        rpId,
      }),
    ).toThrow(
      'Attestation format is "none" but attestation verification is required',
    )
  })

  test('serializeResponse → deserializeResponse round-trip', async () => {
    const { credential, challenge, origin, rpId } = await mockCreateCredential()

    const response = Registration.verify({
      credential,
      challenge,
      origin,
      rpId,
    })

    const serialized = Registration.serializeResponse(response)

    expect(typeof serialized.credential.attestationObject).toBe('string')
    expect(typeof serialized.credential.clientDataJSON).toBe('string')
    expect(typeof serialized.credential.publicKey).toBe('string')
    expect(serialized.credential.id).toBe(credential.id)
    expect(serialized.counter).toBe(0)
    expect(serialized.userVerified).toBe(true)

    const json = JSON.stringify(serialized)
    const deserialized = Registration.deserializeResponse(JSON.parse(json))

    expect(deserialized.credential.id).toBe(credential.id)
    expect(deserialized.credential.publicKey.x).toBe(credential.publicKey.x)
    expect(deserialized.credential.publicKey.y).toBe(credential.publicKey.y)
    expect(deserialized.counter).toBe(response.counter)
    expect(deserialized.userVerified).toBe(response.userVerified)
  })
})
