new test contracts

This commit is contained in:
2026-02-26 19:29:07 +03:00
parent a8f7147500
commit 81d62c1345
10 changed files with 143 additions and 135 deletions

View File

@@ -5,12 +5,75 @@
# @LAYER: Domain (Core)
# @RELATION: DEPENDS_ON -> [DEF:Infra:PostgresDB]
# @RELATION: DEPENDS_ON -> [DEF:Infra:AuditLog]
#
# @INVARIANT: Total system balance must remain constant (Double-Entry Bookkeeping).
# @INVARIANT: Negative transfers are strictly forbidden.
# @INVARIANT: No partial commit must occur under failure (ACID Atomicity).
# @TEST_CONTRACT: TransferInput ->
# {
# required_fields: {
# sender_id: str,
# receiver_id: str,
# amount: Decimal
# },
# invariants: [
# "amount > 0",
# "sender_id != receiver_id"
# ],
# constraints: [
# "sender must exist",
# "receiver must exist"
# ]
# }
# @TEST_CONTRACT: TransferResult ->
# {
# required_fields: {
# tx_id: str,
# status: str,
# new_balance: Decimal
# },
# invariants: [
# "status == COMPLETED implies balance mutation occurred"
# ]
# }
# @TEST_FIXTURE: sufficient_funds ->
# {
# sender_balance: 500.00,
# receiver_balance: 100.00,
# amount: 100.00
# }
# @TEST_EDGE: insufficient_funds ->
# {
# sender_balance: 50.00,
# receiver_balance: 100.00,
# amount: 100.00
# }
#
# @TEST_EDGE: negative_amount ->
# {
# sender_balance: 500.00,
# receiver_balance: 100.00,
# amount: -10.00
# }
#
# @TEST_EDGE: self_transfer ->
# {
# sender_id: "acc_A",
# receiver_id: "acc_A",
# amount: 10.00
# }
# @TEST_EDGE: audit_failure -> raises Exception
# @TEST_EDGE: concurrency_conflict -> special: concurrent_execution
# @TEST_INVARIANT: total_balance_constant -> verifies: [sufficient_funds, concurrency_conflict]
# @TEST_INVARIANT: no_partial_commit -> verifies: [audit_failure]
# @TEST_INVARIANT: negative_transfer_forbidden -> verifies: [negative_amount]
# @TEST_DATA: sufficient_funds -> {"from": "acc_A", "to": "acc_B", "amt": 100.00}
# @TEST_DATA: insufficient_funds -> {"from": "acc_empty", "to": "acc_B", "amt": 1000.00}
# @TEST_DATA: concurrency_lock -> {./fixtures/transactions.json#race_condition}
from decimal import Decimal
from typing import NamedTuple

View File

@@ -1,24 +1,67 @@
<!-- [DEF:FrontendComponentShot:Component] -->
<!-- /**
* @TIER: CRITICAL
* @SEMANTICS: Task, Button, Action, UX
* @PURPOSE: Action button to spawn a new task with full UX feedback cycle.
* @LAYER: UI (Presentation)
* @RELATION: CALLS -> postApi
* @INVARIANT: Must prevent double-submission while loading.
*
* @TEST_DATA: idle_state -> {"isLoading": false}
* @TEST_DATA: loading_state -> {"isLoading": true}
*
* @UX_STATE: Idle -> Button enabled, primary color.
* @UX_STATE: Loading -> Button disabled, spinner visible.
* @UX_STATE: Error -> Toast notification triggers.
*
* @UX_FEEDBACK: Toast success/error.
* @UX_TEST: Idle -> {click: spawnTask, expected: isLoading=true}
* @UX_TEST: Success -> {api_resolve: 200, expected: toast.success called}
*/
-->
<!--
/**
* @TIER: CRITICAL
* @SEMANTICS: Task, Button, Action, UX
* @PURPOSE: Action button to spawn a new task with full UX feedback cycle.
* @LAYER: UI (Presentation)
* @RELATION: CALLS -> postApi
*
* @INVARIANT: Must prevent double-submission while loading.
* @INVARIANT: Loading state must always terminate (no infinite spinner).
* @INVARIANT: User must receive feedback on both success and failure.
*
* @TEST_CONTRACT: ComponentState ->
* {
* required_fields: {
* isLoading: bool
* },
* invariants: [
* "isLoading=true implies button.disabled=true",
* "isLoading=true implies aria-busy=true",
* "isLoading=true implies spinner visible"
* ]
* }
*
* @TEST_CONTRACT: ApiResponse ->
* {
* required_fields: {},
* optional_fields: {
* task_id: str
* }
* }
* @TEST_FIXTURE: idle_state ->
* {
* isLoading: false
* }
*
* @TEST_FIXTURE: successful_response ->
* {
* task_id: "task_123"
* }
* @TEST_EDGE: api_failure -> raises Error("Network")
* @TEST_EDGE: empty_response -> {}
* @TEST_EDGE: rapid_double_click -> special: concurrent_click
* @TEST_EDGE: unresolved_promise -> special: pending_state
* @TEST_INVARIANT: prevent_double_submission -> verifies: [rapid_double_click]
* @TEST_INVARIANT: loading_state_consistency -> verifies: [idle_state, pending_state]
* @TEST_INVARIANT: feedback_always_emitted -> verifies: [successful_response, api_failure]
* @UX_STATE: Idle -> Button enabled, primary color, no spinner.
* @UX_STATE: Loading -> Button disabled, spinner visible, aria-busy=true.
* @UX_STATE: Success -> Toast success displayed.
* @UX_STATE: Error -> Toast error displayed.
*
* @UX_FEEDBACK: toast.success, toast.error
*
* @UX_TEST: Idle -> {click: spawnTask, expected: isLoading=true}
* @UX_TEST: Loading -> {double_click: ignored, expected: single_api_call}
* @UX_TEST: Success -> {api_resolve: task_id, expected: toast.success called}
* @UX_TEST: Error -> {api_reject: error, expected: toast.error called}
-->
<script>
import { postApi } from "$lib/api.js";
import { t } from "$lib/i18n";