OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / identity-operations.test.js

208 lines · javascript ref:
import test from 'brittle'
import {
  encodeOp, decodeOp, validateOp,
  OP_CREATE_USER, OP_UPDATE_USER,
  OP_CREATE_IDENTITY, OP_LINK_IDENTITY, OP_UNLINK_IDENTITY,
  OP_REVOKE_IDENTITY, OP_RESTORE_IDENTITY,
  OP_UPGRADE_PERMISSIONS, OP_MERGE_ACCOUNTS
} from '../../lib/auth/identity-operations.js'

// ========================================
// Encode / Decode
// ========================================

test('encode returns a Buffer', async (t) => {
  const op = { type: OP_CREATE_USER, userId: 'u1', timestamp: Date.now() }
  const buf = encodeOp(op)
  t.ok(Buffer.isBuffer(buf), 'encode returns Buffer')
})

test('encode/decode roundtrip preserves all fields', async (t) => {
  const op = {
    type: OP_CREATE_IDENTITY,
    identityId: 'id1',
    method: 'ssh-key',
    timestamp: 1234567890,
    extra: 'data'
  }
  const decoded = decodeOp(encodeOp(op))
  t.is(decoded.type, op.type)
  t.is(decoded.identityId, op.identityId)
  t.is(decoded.method, op.method)
  t.is(decoded.timestamp, op.timestamp)
  t.is(decoded.extra, op.extra)
})

// ========================================
// Validation — valid operations
// ========================================

test('validate: create-user accepts valid op', async (t) => {
  const res = validateOp({ type: OP_CREATE_USER, userId: 'u1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: update-user accepts valid op', async (t) => {
  const res = validateOp({ type: OP_UPDATE_USER, userId: 'u1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: create-identity accepts all valid methods', async (t) => {
  for (const method of ['web-identity', 'ssh-key', 'keet-identity', 'noise-key']) {
    const res = validateOp({ type: OP_CREATE_IDENTITY, identityId: 'id1', method, timestamp: 1 })
    t.is(res.valid, true, `method ${method} accepted`)
  }
})

test('validate: link-identity accepts valid op', async (t) => {
  const res = validateOp({ type: OP_LINK_IDENTITY, identityId: 'id1', userId: 'u1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: unlink-identity accepts valid op', async (t) => {
  const res = validateOp({ type: OP_UNLINK_IDENTITY, identityId: 'id1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: revoke-identity accepts valid op', async (t) => {
  const res = validateOp({ type: OP_REVOKE_IDENTITY, identityId: 'id1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: restore-identity accepts valid op', async (t) => {
  const res = validateOp({ type: OP_RESTORE_IDENTITY, identityId: 'id1', timestamp: 1 })
  t.is(res.valid, true)
})

test('validate: upgrade-permissions accepts valid op', async (t) => {
  const res = validateOp({
    type: OP_UPGRADE_PERMISSIONS,
    userId: 'u1',
    repoName: 'repo1',
    permissions: ['write', 'admin'],
    timestamp: 1
  })
  t.is(res.valid, true)
})

test('validate: merge-accounts accepts valid op', async (t) => {
  const res = validateOp({
    type: OP_MERGE_ACCOUNTS,
    primaryUserId: 'u1',
    secondaryUserId: 'u2',
    timestamp: 1
  })
  t.is(res.valid, true)
})

// ========================================
// Validation — missing fields
// ========================================

test('validate: rejects missing type', async (t) => {
  t.is(validateOp({}).valid, false)
  t.is(validateOp(null).valid, false)
})

test('validate: rejects unknown type', async (t) => {
  const res = validateOp({ type: 'unknown-op', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('Unknown operation type'))
})

test('validate: rejects missing timestamp', async (t) => {
  const res = validateOp({ type: OP_CREATE_USER, userId: 'u1' })
  t.is(res.valid, false)
  t.ok(res.error.includes('timestamp'))
})

test('validate: create-user rejects missing userId', async (t) => {
  const res = validateOp({ type: OP_CREATE_USER, timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('userId'))
})

test('validate: update-user rejects missing userId', async (t) => {
  const res = validateOp({ type: OP_UPDATE_USER, timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('userId'))
})

test('validate: create-identity rejects missing identityId', async (t) => {
  const res = validateOp({ type: OP_CREATE_IDENTITY, method: 'ssh-key', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('identityId'))
})

test('validate: create-identity rejects invalid method', async (t) => {
  const res = validateOp({ type: OP_CREATE_IDENTITY, identityId: 'id1', method: 'github-oauth', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('method'))
})

test('validate: create-identity rejects missing method', async (t) => {
  const res = validateOp({ type: OP_CREATE_IDENTITY, identityId: 'id1', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('method'))
})

test('validate: link-identity rejects missing identityId', async (t) => {
  const res = validateOp({ type: OP_LINK_IDENTITY, userId: 'u1', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('identityId'))
})

test('validate: link-identity rejects missing userId', async (t) => {
  const res = validateOp({ type: OP_LINK_IDENTITY, identityId: 'id1', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('userId'))
})

test('validate: unlink-identity rejects missing identityId', async (t) => {
  const res = validateOp({ type: OP_UNLINK_IDENTITY, timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('identityId'))
})

test('validate: revoke-identity rejects missing identityId', async (t) => {
  const res = validateOp({ type: OP_REVOKE_IDENTITY, timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('identityId'))
})

test('validate: restore-identity rejects missing identityId', async (t) => {
  const res = validateOp({ type: OP_RESTORE_IDENTITY, timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('identityId'))
})

test('validate: upgrade-permissions rejects missing userId', async (t) => {
  const res = validateOp({ type: OP_UPGRADE_PERMISSIONS, repoName: 'r', permissions: [], timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('userId'))
})

test('validate: upgrade-permissions rejects missing repoName', async (t) => {
  const res = validateOp({ type: OP_UPGRADE_PERMISSIONS, userId: 'u1', permissions: [], timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('repoName'))
})

test('validate: upgrade-permissions rejects non-array permissions', async (t) => {
  const res = validateOp({ type: OP_UPGRADE_PERMISSIONS, userId: 'u1', repoName: 'r', permissions: 'write', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('permissions'))
})

test('validate: merge-accounts rejects missing primaryUserId', async (t) => {
  const res = validateOp({ type: OP_MERGE_ACCOUNTS, secondaryUserId: 'u2', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('primaryUserId'))
})

test('validate: merge-accounts rejects missing secondaryUserId', async (t) => {
  const res = validateOp({ type: OP_MERGE_ACCOUNTS, primaryUserId: 'u1', timestamp: 1 })
  t.is(res.valid, false)
  t.ok(res.error.includes('secondaryUserId'))
})