obelisk-test skill

SkillDev tools

Writing unit tests for obelisk using the test-helpers library. Covers test helper crud-txn, expected result construction, and failure testing patterns.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the obelisk-test skill skill

What this skill tells your AI

The instructions your AI receives, as published by jackfoxy/obelisk in .claude/skills/obelisk-test/SKILL.md and read by ahel’s review.

Write unit tests for obelisk database operations using the test-helpers library at desk/lib/test-helpers.hoon. Tests live in desk/tests/lib/.

File structure

Every test file:

  1. Imports test-helpers: /+ *test-helpers
  2. Opens a core: |%
  3. Optionally defines reusable data (table DDL, insert statements, expected row sets) as arms before the test arms
  4. Each test arm is named ++ test-<feature>-<nn> for success tests or ++ test-fail-<feature>-<nn> for failure tests
  5. Closes with --

Some test files define a local bowl and state in a door before |% (see delete.hoon). Most test files rely on the bowl and state from test-helpers directly.

Test helper naming convention: exec-<actions>-<resolves>

The helper name encodes the test shape:

  • actions = number of setup/mutation pokes after init (0, 1, 2, ... 9)
  • resolves = number of verification pokes at the end (r, l, 1, 2, ... 6)

Resolve suffixes:

  • r = single cmd-result comparison via eval-results
  • l = single (list cmd-result) comparison via expect-eq
  • 1, 2, ... 6 = that many cmd-result comparisons via eval-results (welded/zinged)
  • ls = first resolve is (list cmd-result), second is cmd-result
  • ll = both resolves are (list cmd-result)

Common helpers and when to use them

HelperShapeUse case
exec-0-rinit, compare cmd-result onlyDDL-only tests (CREATE DATABASE, CREATE TABLE)
exec-0-linit, compare (list cmd-result)Multi-result init
exec-0-1init + 1 resolveSchema setup then query
exec-0-022 resolves (no init poke)Two independent queries
exec-1-1init + 1 action + 1 resolveSetup, mutate, verify
exec-1-2init + 1 action + 2 resolvesSetup, mutate, verify two things
exec-2-1init + 2 actions + 1 resolveSetup, two mutations, verify
exec-2-2init + 2 actions + 2 resolvesSetup, two mutations, two verifications
exec-3-1init + 3 actions + 1 resolveThree setup steps then verify
exec-5-1init + 5 actions + 1 resolveComplex multi-step setup

Failure helpers

HelperShapeUse case
failon-0init should crashInit poke expected to fail
failon-1init + action that should crashSetup then failing action
failon-1cinit + action (raw action type) crashesFailing typed action poke
failon-1ccinit (raw action) + action (raw action) crashesBoth pokes use raw action type
failon-cinit + action (embedded =action) crashesVariant with embedded action face
failon-2init + 1 action + failing 2nd actionTwo actions, second fails
failon-2cinit (raw) + 1 action (raw) + failing action (raw)All raw action types
failon-3init + 2 actions + failing 3rd actionThree actions, third fails
failon-3cinit + 2 actions + failing 3rd (raw action)Mixed tape/raw
failon-4init + 3 actions + failing 4th actionFour actions, fourth fails

Debug helpers

Replace exec- with debug- (e.g., debug-0-1, debug-2-1) to get crash output instead of comparison. These use %test mark and expect-fail-message with 'placeholder for debugging'.

Poke tuple format

Each poke (init, action, resolve) is a triple:

[tmsp=@da db=@tas uql=tape]
  • tmsp: timestamp for the bowl (simulates server time)
  • db: database name (use %sys for system-level commands like CREATE DATABASE, %db1 for database-scoped commands)
  • uql: the urQL command string as a tape

For raw action pokes (in failon-1c, failon-1cc, exec-0-0c2, exec-5-2xx):

[tmsp=@da cmds=action]

Constructing expected results

cmd-result structure

A cmd-result is a tagged union with %results head followed by a list of result entries:

:-  %results
    :~  [%action 'SELECT']           :: or 'INSERT INTO db1.dbo.my-table', etc.
        [%result-set <list of vectors>]    :: only for SELECT results
        [%server-time ~2012.5.3]
        [%relation-name 'db1.dbo.my-table']      :: source table(s)
        [%schema-time ~2012.5.1]
        [%data-time ~2012.5.2]
        [%vector-count 3]
        ==

For JOINed queries, each source table gets its own message/schema-time/data-time block:

[%relation-name 'db1.dbo.calendar']
[%schema-time ~2012.4.30]
[%data-time ~2012.4.30]
[%relation-name 'db1.dbo.holiday-calendar']
[%schema-time ~2012.4.30]
[%data-time ~2012.4.30]

Result vectors

Each row is a %vector containing a list of [column-name [aura value]] cells:

:-  %vector
    :~  [%col1 [~.t 'cord']]
        [%col2 [~.p ~nomryg-nilref]]
        [%col3 [~.ud 20]]
        [%col4 [~.da ~2010.6.1]]
        ==

Aura tags use ~. prefix: ~.t for @t, ~.da for @da, ~.p for @p, ~.ud for @ud, etc.

Default values when not explicitly set: @da defaults to ~2000.1.1, @t defaults to 'Default', @p defaults to ~zod, @ud defaults to 0.

INSERT result pattern

INSERT results do not contain a %result-set. Instead:

:-  %results
    :~  [%action 'INSERT INTO db1.dbo.my-table']
        [%server-time ~2012.5.3]
        [%schema-time ~2012.5.1]
        [%data-time ~2012.5.1]
        [%message 'inserted:']
        [%vector-count 2]
        [%message 'table data:']
        [%vector-count 2]
        ==

Failure result pattern

For failon-* helpers, the expect argument is a @t cord with the expected error message:

%-  crip  "%date is duplicate column name in "
          "common table expression %my-cte"

Or simply:

'expected error message'

Complete test examples

Simple query test (exec-0-1: init then resolve)

++  test-cte-00
  =|  run=@ud
  %-  exec-0-1
        :*  run
            :+  ~2012.4.30
                %db1
                %-  zing  :~  "CREATE DATABASE db1;"
                              create-table
                              insert-table
                              ==
            ::
            :+  ~2012.5.3
                %db1
                "FROM my-table SELECT *"
            ::
            :-  %results  :~  [%action 'SELECT']
                              [%result-set expected-rows]
                              [%server-time ~2012.5.3]
                              [%relation-name 'db1.dbo.my-table']
                              [%schema-time ~2012.4.30]
                              [%data-time ~2012.4.30]
                              [%vector-count 7]
                              ==
            ==

Insert + verify test (exec-1-2: init + action + 2 resolves)

++  test-insert-01
  =|  run=@ud
  %-  exec-1-2
  :*  run
      [~2012.4.30 %sys "CREATE DATABASE db1"]
      ::
      :+  ~2012.5.1
          %db1
          "CREATE TABLE db1..my-table ".
          "(col1 @t, col2 @p, col3 @ud) ".
          "PRIMARY KEY (col1)"
      ::
      :+  ~2012.5.3
          %db1
          "INSERT INTO db1..my-table (col1, col2, col3)  ".
          "VALUES ('cord',~nomryg-nilref,20) ('Default',Default, 0)"
      ::
      [~2012.5.4 %db1 "FROM my-table SELECT *"]
      ::
      :-  %results  :: expect-1 (INSERT result)
          :~  [%action 'INSERT INTO db1.dbo.my-table']
              [%server-time ~2012.5.3]
              [%schema-time ~2012.5.1]
              [%data-time ~2012.5.1]
              [%message 'inserted:']
              [%vector-count 2]
              [%message 'table data:']
              [%vector-count 2]
              ==
      ::
      :-  %results  :: expect-2 (SELECT result)
          :~  [%action 'SELECT']
              [%result-set expected-2-rows]
              [%server-time ~2012.5.4]
              [%relation-name 'db1.dbo.my-table']
              [%schema-time ~2012.5.1]
              [%data-time ~2012.5.3]
              [%vector-count 2]
              ==
      ==

Failure test (failon-1: init + failing action)

++  test-fail-cte-00
  =|  run=@ud
  %-  failon-1  :*  run
                    :+  ~2012.4.30
                        %db1
                        %-  zing  :~  "CREATE DATABASE db1;"
                                create-calendar
                                insert-calendar
                                ==
                    ::
                    :+  ~2012.5.5
                        %db1
                        "FROM bad-table SELECT *"
                    ::
                    'expected error message'
                    ==

Combining multiple urQL statements in init

Use zing to concatenate multiple urQL statements (each ending with ;) into a single tape for the init poke:

:+  ~2012.4.30
    %db1
    %-  zing  :~  "CREATE DATABASE db1;"
                  create-table
                  insert-table
                  ==

Reusable DDL/DML arms

Define reusable DDL and DML as tape arms at the top of the test file:

++  create-table   "CREATE TABLE db1..my-table ".
                   "(col0 @da, col1 @t, col2 @p) ".
                   "PRIMARY KEY (col0, col2);"
::
++  insert-table   "INSERT INTO db1..my-table (col0, col1, col2) ".
                   "VALUES (~2010.5.3, 'cord', ~nomryg-nilref);"

Note: DDL/DML arms that will be zinged into an init must end with ; followed by a space or be followed by statements that do.

Timestamp conventions

  • Init timestamps typically start at ~2012.4.30
  • Subsequent action timestamps increment: ~2012.5.1, ~2012.5.2, ~2012.5.3, etc.
  • The schema-time in expected results matches when the schema was created
  • The data-time matches when data was last modified
  • The server-time matches the resolve poke's timestamp

Signals

GitHub stars
37
Forks
3
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
obelisk-test
Source
github.com/jackfoxy/obelisk