harn-hostlib 0.8.103

Opt-in code-intelligence and deterministic-tool host builtins for the Harn VM
Documentation
package com.burinexample

import java.time.Instant
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertFailsWith

class TaskWorkflowTest {
    private val service = TaskService()

    @Test
    fun newTaskStartsInTODO() {
        val task = service.create("New task")
        assertEquals(TaskStatus.TODO, task.status)
        assertNull(task.completedAt)
    }

    @Test
    fun validPath_TODO_to_IN_PROGRESS_to_DONE() {
        val task = service.create("Long task")
        assertEquals(TaskStatus.TODO, task.status)

        val inProgress = service.transition(task.id, TaskStatus.IN_PROGRESS)
        assertEquals(TaskStatus.IN_PROGRESS, inProgress.status)
        assertNull(inProgress.completedAt)

        val done = service.transition(task.id, TaskStatus.DONE)
        assertEquals(TaskStatus.DONE, done.status)
        assertNotNull(done.completedAt)
    }

    @Test
    fun validPath_TODO_to_CANCELLED() {
        val task = service.create("Short task")
        assertEquals(TaskStatus.TODO, task.status)

        val cancelled = service.transition(task.id, TaskStatus.CANCELLED)
        assertEquals(TaskStatus.CANCELLED, cancelled.status)
        assertNull(cancelled.completedAt)
    }

    @Test
    fun transition_DONE_to_TODO_throws() {
        val task = service.create("Done task")
        service.transition(task.id, TaskStatus.DONE)

        assertFailsWith<IllegalArgumentException> {
            service.transition(task.id, TaskStatus.TODO)
        }
    }

    @Test
    fun parallel_transitions_FROM_IN_PROGRESS_to_DONE() {
        val task1 = service.create("Task A")
        service.transition(task1.id, TaskStatus.IN_PROGRESS)

        val done = service.transition(task1.id, TaskStatus.DONE)
        assertEquals(TaskStatus.DONE, done.status)
        assertNotNull(done.completedAt)
    }

    @Test
    fun parallel_transitions_FROM_IN_PROGRESS_to_CANCELLED() {
        val task1 = service.create("Task B")
        service.transition(task1.id, TaskStatus.IN_PROGRESS)

        val cancelled = service.transition(task1.id, TaskStatus.CANCELLED)
        assertEquals(TaskStatus.CANCELLED, cancelled.status)
        assertNull(cancelled.completedAt)
    }

    @Test
    fun completedAt_is_set_only_on_DONE() {
        // TODO status: no completedAt
        val todoTask = service.create("Todo task")
        assertNull(todoTask.completedAt)

        // IN_PROGRESS status: no completedAt
        val inProgressTask = service.create("In progress task")
        service.transition(inProgressTask.id, TaskStatus.IN_PROGRESS)
        assertNull(inProgressTask.completedAt)

        // DONE status: completedAt is set
        val doneTask = service.create("Done task")
        service.transition(doneTask.id, TaskStatus.DONE)
        assertNotNull(doneTask.completedAt)

        // CANCELLED status: completedAt is null
        val cancelledTask = service.create("Cancelled task")
        service.transition(cancelledTask.id, TaskStatus.CANCELLED)
        assertNull(cancelledTask.completedAt)
    }

    @Test
    fun transition_DONE_to_CANCELLED_throws() {
        val task = service.create("Done task")
        service.transition(task.id, TaskStatus.DONE)

        assertFailsWith<IllegalArgumentException> {
            service.transition(task.id, TaskStatus.CANCELLED)
        }
    }

    @Test
    fun transition_CANCELLED_to_TODO_throws() {
        val task = service.create("Cancelled task")
        service.transition(task.id, TaskStatus.CANCELLED)

        assertFailsWith<IllegalArgumentException> {
            service.transition(task.id, TaskStatus.TODO)
        }
    }

    @Test
    fun transition_CANCELLED_to_IN_PROGRESS_throws() {
        val task = service.create("Cancelled task")
        service.transition(task.id, TaskStatus.CANCELLED)

        assertFailsWith<IllegalArgumentException> {
            service.transition(task.id, TaskStatus.IN_PROGRESS)
        }
    }

    @Test
    fun completedAt_is_null_before_DONE() {
        val task = service.create("Multi-step task")
        assertNull(task.completedAt)

        service.transition(task.id, TaskStatus.IN_PROGRESS)
        assertNull(task.completedAt)
    }

    @Test
    fun completedAt_is_set_after_DONE() {
        val before = Instant.now()
        val task = service.create("Final task")
        service.transition(task.id, TaskStatus.IN_PROGRESS)
        service.transition(task.id, TaskStatus.DONE)
        val after = Instant.now()

        val completedAt = task.completedAt
        assertNotNull(completedAt)
        assertEquals(true, completedAt.isAfter(before) || completedAt == before)
        assertEquals(true, completedAt.isBefore(after) || completedAt == after)
    }
}