Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/tests/contract/pi_rpc_spec.lua

Raw
---@diagnostic disable: need-check-nil, undefined-field
-- Guards the Pi assumptions pivi is built on.
-- The child runs with the user's own Pi configuration, because pivi's promise
-- is bring your own Pi: extensions, skills, prompts, and providers keep loading
-- under Pi's normal rules.
-- These run a real `pi --mode rpc` child, so they are opt in:
--   PIVI_CONTRACT=1 just test tests/contract
-- They make no model call, so they cost nothing beyond process startup.

local jsonl = require 'bugabinga.pivi.jsonl'
local rpc = require 'bugabinga.pivi.rpc'
local help = require 'bugabinga.pivi.help'
local runtime = require 'bugabinga.pivi.runtime'

local enabled = vim.env.PIVI_CONTRACT == '1' and vim.fn.executable 'pi' == 1

-- Starting Pi with the user's full configuration is the expensive part, so one
-- child per command line serves every question asked of it.
local children = {}

local child_for = function( label, command_line )
  if children[label] then return children[label] end

  local child = { frames = {}, }
  child.client = rpc.start {
    command = command_line,
    cwd = vim.uv.cwd(),
    on_event = function( frame ) table.insert( child.frames, frame ) end,
    on_response = function( frame ) table.insert( child.frames, frame ) end,
  }
  children[label] = child
  return child
end

local ask = function( commands, predicate, command_line )
  local child = child_for(
    command_line and 'help' or 'default',
    command_line or { 'pi', '--mode', 'rpc', '--no-session', }
  )

  local first = #child.frames + 1
  for _, command in ipairs( commands ) do
    child.client.request( command )
  end

  local since = function()
    return vim.list_slice( child.frames, first, #child.frames )
  end

  vim.wait( 120000, function() return predicate( since() ) end, 50 )
  return since()
end

local shutdown = function()
  for _, child in pairs( children ) do
    child.client.stop()
    -- A child that outlives the run keeps the whole suite from exiting.
    vim.wait( 15000, function() return not child.client.is_running() end, 50 )
  end
  children = {}
end

local response_for = function( frames, name )
  for _, frame in ipairs( frames ) do
    if frame.type == 'response' and frame.command == name then return frame end
  end
  return nil
end

describe( 'pi rpc contract', function()
  if not enabled then
    it( 'is skipped without PIVI_CONTRACT=1 and pi on PATH', function()
      assert.is_true( true )
    end )
    return
  end

  it( 'answers get_state with the fields pivi caches', function()
    local frames = ask( { { type = 'get_state', }, }, function( seen )
      return response_for( seen, 'get_state' ) ~= nil
    end )

    local answer = response_for( frames, 'get_state' )
    assert.is_true( answer.success )
    assert.is_boolean( answer.data.isStreaming )
    assert.is_boolean( answer.data.isCompacting )
    assert.is_string( answer.data.thinkingLevel )
  end )

  it( 'lists only commands a non interactive client can invoke', function()
    local frames = ask( { { type = 'get_commands', }, }, function( seen )
      return response_for( seen, 'get_commands' ) ~= nil
    end )

    local answer = response_for( frames, 'get_commands' )
    assert.is_true( answer.success )
    for _, entry in ipairs( answer.data.commands ) do
      assert.is_string( entry.name )
      assert.is_true( entry.source == 'extension' or entry.source == 'prompt' or entry.source == 'skill' )
      assert.is_not.equal( 'settings', entry.name )
      assert.is_not.equal( 'hotkeys', entry.name )
    end
  end )

  it( 'reports a failed command instead of closing the channel', function()
    local frames = ask( {
      { type = 'set_model', provider = 'nope', modelId = 'nope', },
      { type = 'get_available_thinking_levels', },
    }, function( seen )
      return response_for( seen, 'get_available_thinking_levels' ) ~= nil
    end )

    local failed = response_for( frames, 'set_model' )
    assert.is_false( failed.success )
    assert.is_string( failed.error )
    assert.is_true( response_for( frames, 'get_available_thinking_levels' ).success )
  end )

  it( 'keeps the user configuration loaded', function()
    local frames = ask( { { type = 'get_commands', }, }, function( seen )
      return response_for( seen, 'get_commands' ) ~= nil
    end )

    local sources = {}
    for _, entry in ipairs( response_for( frames, 'get_commands' ).data.commands ) do
      sources[entry.source] = true
    end

    assert.is_true( next( sources ) ~= nil )
  end )

  it( 'accepts the flags a help run uses and loads the pivi skill', function()
    local frames = ask(
      { { type = 'get_commands', }, },
      function( seen ) return response_for( seen, 'get_commands' ) ~= nil end,
      help.build_command {}
    )

    local answer = response_for( frames, 'get_commands' )
    assert.is_true( answer.success )

    local names = {}
    for _, entry in ipairs( answer.data.commands ) do
      names[entry.name] = true
    end
    assert.is_true( names['skill:pivi'] )

    for _, frame in ipairs( frames ) do
      assert.is_not.equal( 'extension_error', frame.type )
    end
  end )

  it( 'loads the companion extension without error', function()
    assert.is_string( runtime.extension_path() )
    assert.is_string( runtime.skill_path() )
  end )

  after_each( function()
    -- Every question must be answerable by an already running child.
    for _, child in pairs( children ) do
      assert.is_true( child.client.is_running() )
    end
  end )

  it( 'frames every record as strict line feed delimited JSON', function()
    local frames = ask( { { type = 'get_state', }, }, function( seen )
      return response_for( seen, 'get_state' ) ~= nil
    end )
    assert.is_true( #frames > 0 )
    for _, frame in ipairs( frames ) do
      local encoded = vim.json.encode( frame )
      assert.is_nil( encoded:find( '\n', 1, true ) )
      assert.is_table( jsonl.parse( encoded ) )
    end
  end )

  it( 'ends every child it started', function()
    shutdown()
    assert.same( {}, children )
  end )
end )