Skip to content

Commit 437f80e

Browse files
committed
ported expense report test from Go
1 parent 14d1e61 commit 437f80e

14 files changed

Lines changed: 2133 additions & 0 deletions

expense/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Expense
2+
3+
This sample workflow processes an expense request. The key part of this sample is to show how to complete an activity asynchronously.
4+
5+
## Sample Description
6+
7+
* Create a new expense report.
8+
* Wait for the expense report to be approved. This could take an arbitrary amount of time. So the activity's `execute` method has to return before it is actually approved. This is done by raising `activity.AsyncActivityCompleteError` so the framework knows the activity is not completed yet.
9+
* When the expense is approved (or rejected), somewhere in the world needs to be notified, and it will need to call `client.get_async_activity_handle().complete()` to tell Temporal service that the activity is now completed.
10+
In this sample case, the sample expense system does this job. In real world, you will need to register some listener to the expense system or you will need to have your own polling agent to check for the expense status periodically.
11+
* After the wait activity is completed, it does the payment for the expense (UI step in this sample case).
12+
13+
This sample relies on a sample expense system to work.
14+
15+
## Steps To Run Sample
16+
17+
* You need a Temporal service running. See the main [README.md](../README.md) for more details.
18+
* Start the sample expense system UI:
19+
```bash
20+
uv run -m expense.ui
21+
```
22+
* Start workflow and activity workers:
23+
```bash
24+
uv run -m expense.worker
25+
```
26+
* Start expense workflow execution:
27+
```bash
28+
uv run -m expense.starter
29+
```
30+
* When you see the console print out that the expense is created, go to [localhost:8099/list](http://localhost:8099/list) to approve the expense.
31+
* You should see the workflow complete after you approve the expense. You can also reject the expense.
32+
* If you see the workflow failed, try to change to a different port number in `ui.py` and `activities.py`. Then rerun everything.
33+
34+
## Running Tests
35+
36+
```bash
37+
# Run all tests
38+
uv run pytest expense/test_workflow.py -v
39+
40+
# Run a specific test
41+
uv run pytest expense/test_workflow.py::TestSampleExpenseWorkflow::test_workflow_with_mock_activities -v
42+
```
43+
44+
## Key Concepts Demonstrated
45+
46+
* **Async Activity Completion**: Using `activity.raise_complete_async()` to indicate an activity will complete asynchronously
47+
* **Human-in-the-Loop Workflows**: Long-running workflows that wait for human interaction
48+
* **External System Integration**: HTTP-based communication between activities and external systems
49+
* **Task Tokens**: Using task tokens to complete activities from external systems
50+
* **Web UI Integration**: FastAPI-based expense approval system
51+
52+
## Files
53+
54+
* `workflow.py` - The main expense processing workflow
55+
* `activities.py` - Three activities: create expense, wait for decision, process payment
56+
* `ui.py` - FastAPI-based mock expense system with web UI
57+
* `worker.py` - Worker to run workflows and activities
58+
* `starter.py` - Client to start workflow executions
59+
* `test_workflow.py` - Unit tests with mocked activities

expense/UI_SPECIFICATION.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Expense System UI Specification
2+
3+
## Overview
4+
The Expense System UI is a FastAPI-based web application that provides both a web interface and REST API for managing expense requests. It integrates with Temporal workflows through callback mechanisms.
5+
6+
## System Components
7+
8+
### Data Model
9+
- **ExpenseState Enum**: Defines expense lifecycle states
10+
- `CREATED`: Initial state when expense is first created
11+
- `APPROVED`: Expense has been approved for payment
12+
- `REJECTED`: Expense has been denied
13+
- `COMPLETED`: Payment has been processed
14+
15+
### Storage
16+
- **all_expenses**: In-memory dictionary mapping expense IDs to their current state
17+
- **token_map**: Maps expense IDs to Temporal activity task tokens for workflow callbacks
18+
19+
## API Endpoints
20+
21+
### 1. Home/List View (`GET /` or `GET /list`)
22+
**Purpose**: Display all expenses in an HTML table format
23+
24+
**Response**: HTML page containing:
25+
- Page title "SAMPLE EXPENSE SYSTEM"
26+
- Navigation link to HOME
27+
- Table with columns: Expense ID, Status, Action
28+
- Action buttons for CREATED expenses (APPROVE/REJECT)
29+
- Sorted expense display by ID
30+
31+
**Business Rules**:
32+
- Only CREATED expenses show action buttons
33+
- Expenses are displayed in sorted order by ID
34+
35+
### 2. Action Handler (`GET /action`)
36+
**Purpose**: Process expense state changes (approve/reject/payment)
37+
38+
**Parameters**:
39+
- `type` (required): Action type - "approve", "reject", or "payment"
40+
- `id` (required): Expense ID
41+
- `is_api_call` (optional): "true" for API calls, "false" for UI calls
42+
43+
**Business Rules**:
44+
- `approve`: Changes CREATED → APPROVED
45+
- `reject`: Changes CREATED → REJECTED
46+
- `payment`: Changes APPROVED → COMPLETED
47+
- Invalid IDs return 400 error
48+
- Invalid action types return 400 error
49+
- State changes from CREATED to APPROVED/REJECTED trigger workflow notifications
50+
- API calls return "SUCCEED" on success
51+
- UI calls redirect to list view after success
52+
53+
**Error Handling**:
54+
- API calls return "ERROR:INVALID_ID" or "ERROR:INVALID_TYPE"
55+
- UI calls return HTTP 400 with descriptive messages
56+
57+
### 3. Create Expense (`GET /create`)
58+
**Purpose**: Create a new expense entry
59+
60+
**Parameters**:
61+
- `id` (required): Unique expense ID
62+
- `is_api_call` (optional): "true" for API calls, "false" for UI calls
63+
64+
**Business Rules**:
65+
- Expense ID must be unique
66+
- New expenses start in CREATED state
67+
- Duplicate IDs return 400 error
68+
69+
**Error Handling**:
70+
- API calls return "ERROR:ID_ALREADY_EXISTS"
71+
- UI calls return HTTP 400 with descriptive message
72+
73+
### 4. Status Check (`GET /status`)
74+
**Purpose**: Retrieve current expense state
75+
76+
**Parameters**:
77+
- `id` (required): Expense ID
78+
79+
**Response**: Current expense state as string
80+
**Error Handling**: Returns "ERROR:INVALID_ID" for unknown IDs
81+
82+
### 5. Callback Registration (`POST /registerCallback`)
83+
**Purpose**: Register Temporal workflow callback for expense state changes
84+
85+
**Parameters**:
86+
- `id` (query): Expense ID
87+
- `task_token` (form): Hex-encoded Temporal task token
88+
89+
**Business Rules**:
90+
- Expense must exist and be in CREATED state
91+
- Task token must be valid hex format
92+
- Enables workflow notification on state changes
93+
94+
**Error Handling**:
95+
- "ERROR:INVALID_ID" for unknown expenses
96+
- "ERROR:INVALID_STATE" for non-CREATED expenses
97+
- "ERROR:INVALID_FORM_DATA" for invalid tokens
98+
99+
## Workflow Integration
100+
101+
### Callback Mechanism
102+
- When expenses transition from CREATED to APPROVED/REJECTED, registered callbacks are triggered
103+
- Uses Temporal's async activity completion mechanism
104+
- Task tokens are stored and used to complete workflow activities
105+
106+
### Error Handling
107+
- Failed callback completions are logged but don't affect UI operations
108+
- Invalid or expired tokens are handled gracefully
109+
110+
## User Interface
111+
112+
### Web Interface Features
113+
- Clean HTML table display
114+
- Color-coded action buttons (green for APPROVE, red for REJECT)
115+
- Real-time state display
116+
- Navigation between views
117+
118+
### API Interface Features
119+
- RESTful endpoints for programmatic access
120+
- Consistent error response format
121+
- Support for both sync and async operations
122+
123+
## Non-Functional Requirements
124+
125+
### Concurrency
126+
- Thread-safe in-memory storage operations
127+
- Handles concurrent API and UI requests
128+
129+
### Error Recovery
130+
- Graceful handling of workflow callback failures
131+
- Input validation on all endpoints
132+
- Descriptive error messages
133+
134+
### Logging
135+
- State change operations are logged
136+
- Callback registration and completion logged
137+
- Error conditions logged for debugging
138+
139+
## Security Considerations
140+
- Input validation on all parameters
141+
- Protection against duplicate ID creation
142+
- Secure handling of Temporal task tokens
143+
144+
## Scalability Notes
145+
- Current implementation uses in-memory storage
146+
- Designed for demonstration/development use
147+
- Production deployment would require persistent storage

0 commit comments

Comments
 (0)