Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Test

on:
push:
branches: [master]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v4

- name: Run tests
run: make test
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.PHONY: test

test:
uv run --with behave behave
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ Tickets stored as markdown files in .tickets/
Supports partial ID matching (e.g., 'tk show 5c4' matches 'nw-5c46')
```

## Testing

The tests are written in the Behavior-Driven Development library [behave](https://behave.readthedocs.io/en/latest/) and require Python.

If you have `uv` [installed](https://docs.astral.sh/uv/getting-started/installation/) simply:

```sh
make test
```

## Migrating from Beads

```bash
Expand Down
41 changes: 41 additions & 0 deletions features/environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Behave environment setup for ticket CLI tests."""

import os
import shutil
import tempfile
from pathlib import Path


def before_all(context):
"""Set up test environment before all tests."""
# Store the project directory (where the ticket script lives)
context.project_dir = Path(__file__).parent.parent.resolve()


def before_scenario(context, scenario):
"""Create a fresh temporary directory for each scenario."""
# Create a temporary directory for this scenario
context.test_dir = tempfile.mkdtemp(prefix='ticket_test_')

# Initialize tracking
context.tickets = {}
context.last_created_id = None
context.stdout = ''
context.stderr = ''
context.returncode = None


def after_scenario(context, scenario):
"""Clean up temporary directory after each scenario."""
if hasattr(context, 'test_dir') and os.path.exists(context.test_dir):
shutil.rmtree(context.test_dir)


def before_feature(context, feature):
"""Called before each feature file is processed."""
pass


def after_feature(context, feature):
"""Called after each feature file is processed."""
pass
71 changes: 71 additions & 0 deletions features/id_resolution.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Feature: Ticket ID Resolution
As a user
I want to use partial ticket IDs
So that I can work faster without typing full IDs

Background:
Given a clean tickets directory

Scenario: Exact ID match
Given a ticket exists with ID "abc-1234" and title "Test ticket"
When I run "ticket show abc-1234"
Then the command should succeed
And the output should contain "id: abc-1234"

Scenario: Partial ID match by suffix
Given a ticket exists with ID "abc-1234" and title "Test ticket"
When I run "ticket show 1234"
Then the command should succeed
And the output should contain "id: abc-1234"

Scenario: Partial ID match by prefix
Given a ticket exists with ID "abc-1234" and title "Test ticket"
When I run "ticket show abc"
Then the command should succeed
And the output should contain "id: abc-1234"

Scenario: Partial ID match by substring
Given a ticket exists with ID "abc-1234" and title "Test ticket"
When I run "ticket show c-12"
Then the command should succeed
And the output should contain "id: abc-1234"

Scenario: Ambiguous ID error
Given a ticket exists with ID "abc-1234" and title "First ticket"
And a ticket exists with ID "abc-5678" and title "Second ticket"
When I run "ticket show abc"
Then the command should fail
And the output should contain "Error: ambiguous ID 'abc' matches multiple tickets"

Scenario: Non-existent ID error
When I run "ticket show nonexistent"
Then the command should fail
And the output should contain "Error: ticket 'nonexistent' not found"

Scenario: Exact match takes precedence
Given a ticket exists with ID "abc" and title "Short ID ticket"
And a ticket exists with ID "abc-1234" and title "Long ID ticket"
When I run "ticket show abc"
Then the command should succeed
And the output should contain "id: abc"
And the output should contain "Short ID ticket"

Scenario: ID resolution works with status command
Given a ticket exists with ID "test-9999" and title "Test ticket"
When I run "ticket status 9999 in_progress"
Then the command should succeed
And ticket "test-9999" should have field "status" with value "in_progress"

Scenario: ID resolution works with dep command
Given a ticket exists with ID "dep-aaaa" and title "Main"
And a ticket exists with ID "dep-bbbb" and title "Dependency"
When I run "ticket dep aaaa bbbb"
Then the command should succeed
And ticket "dep-aaaa" should have "bbbb" in deps

Scenario: ID resolution works with link command
Given a ticket exists with ID "link-cccc" and title "First"
And a ticket exists with ID "link-dddd" and title "Second"
When I run "ticket link cccc dddd"
Then the command should succeed
And ticket "link-cccc" should have "link-dddd" in links
1 change: 1 addition & 0 deletions features/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
behave>=1.2.6
Loading