chore: import upstream snapshot with attribution
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# tinystruct Architecture and Configuration
|
||||
|
||||
## When to Use
|
||||
|
||||
Choose **tinystruct** when you need a lightweight, high-performance Java framework that treats CLI and HTTP as equal citizens. Ideal for microservices, CLI utilities, and data-driven applications with a small footprint and zero-dependency JSON handling.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Core Architecture
|
||||
|
||||
The framework operates on a singleton `ActionRegistry` that maps URL patterns (or command strings) to `Action` objects. When a request arrives, the system resolves the path and invokes the corresponding method handle.
|
||||
|
||||
#### Key Abstractions
|
||||
|
||||
| Class/Interface | Role |
|
||||
|---|---|
|
||||
| `AbstractApplication` | Base class for all tinystruct applications. Extend this. |
|
||||
| `@Action` annotation | Maps a method to a URI path (web) or command name (CLI). The single routing primitive. |
|
||||
| `ActionRegistry` | Singleton that maps URL patterns to `Action` objects via regex. Never instantiate directly. |
|
||||
| `Action` | Wraps a `MethodHandle` + regex pattern + priority + `Mode` for dispatch. |
|
||||
| `Context` | Per-request state store. Access via `getContext()`. Holds CLI args and HTTP request/response. |
|
||||
| `Dispatcher` | CLI entry point (`bin/dispatcher`). Reads `--import` to load applications. |
|
||||
| `HttpServer` | Built-in HTTP server. Start with `bin/dispatcher start --import org.tinystruct.system.HttpServer`. |
|
||||
|
||||
### Package Map
|
||||
|
||||
```
|
||||
org.tinystruct/
|
||||
├── AbstractApplication.java ← extend this
|
||||
├── Application.java ← interface
|
||||
├── ApplicationException.java ← checked exception
|
||||
├── ApplicationRuntimeException.java ← unchecked exception
|
||||
├── application/
|
||||
│ ├── Action.java ← runtime action wrapper
|
||||
│ ├── ActionRegistry.java ← singleton route registry
|
||||
│ └── Context.java ← request context
|
||||
├── system/
|
||||
│ ├── annotation/Action.java ← @Action annotation + Mode enum
|
||||
│ ├── Dispatcher.java ← CLI dispatcher
|
||||
│ ├── HttpServer.java ← built-in HTTP server
|
||||
│ ├── EventDispatcher.java ← event bus
|
||||
│ └── Settings.java ← reads application.properties
|
||||
├── data/
|
||||
│ ├── component/Builder.java ← JSON object (use instead of Gson/Jackson)
|
||||
│ ├── component/Builders.java ← JSON array
|
||||
│ ├── component/AbstractData.java ← base POJO for DB persistence
|
||||
│ ├── component/Condition.java ← fluent SQL query builder
|
||||
│ ├── component/FieldType.java ← SQL-to-Java type mappings
|
||||
│ ├── Mapping.java ← reads .map.xml metadata
|
||||
│ ├── DatabaseOperator.java ← low-level JDBC wrapper
|
||||
│ └── FileEntity.java ← file upload representation
|
||||
├── http/ ← Request, Response, Constants
|
||||
│ └── SSEPushManager.java ← Server-Sent Events management
|
||||
└── net/ ← URLRequest, HTTPHandler (outbound HTTP)
|
||||
```
|
||||
|
||||
### Template Behavior and Dispatch Flow
|
||||
|
||||
By default, the framework assumes a view template is required. If `templateRequired` is `true`, `toString()` looks for a `.view` file in `src/main/resources/themes/<ClassName>.view`. Use `setVariable("name", value)` to pass data to templates, which use `{%name%}` for interpolation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal Application Initialization
|
||||
```java
|
||||
@Override
|
||||
public void init() {
|
||||
this.setTemplateRequired(false); // Skip .view template lookup for data-only apps
|
||||
// Do NOT call setAction() here — use @Action annotation instead
|
||||
}
|
||||
```
|
||||
|
||||
### Action Definition and CLI Invocation
|
||||
```java
|
||||
@Action("hello")
|
||||
public String hello() {
|
||||
return "Hello, tinystruct!";
|
||||
}
|
||||
```
|
||||
**Execution via Dispatcher:**
|
||||
```bash
|
||||
bin/dispatcher hello
|
||||
bin/dispatcher greet/James
|
||||
bin/dispatcher echo --words "Hello" --import com.example.HelloApp
|
||||
```
|
||||
|
||||
### Configuration Access
|
||||
Located at `src/main/resources/application.properties`:
|
||||
```java
|
||||
String port = this.getConfiguration("server.port");
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
# tinystruct Data Handling (JSON)
|
||||
|
||||
## When to Use
|
||||
|
||||
Prefer `org.tinystruct.data.component.Builder` and `Builders` for lightweight, zero-dependency JSON. Use `Builder` for JSON objects (`{}`), `Builders` for JSON arrays (`[]`). **Always use `Builders` instead of `List<Builder>`** to avoid generic type erasure issues.
|
||||
|
||||
## How It Works
|
||||
|
||||
`Builder` provides a key-value interface for creating and reading JSON objects. `Builders` provides an indexed list for JSON arrays. Both integrate directly with `AbstractApplication` result handling.
|
||||
|
||||
### Why Builder/Builders?
|
||||
- **Zero External Dependencies** — lean and fast
|
||||
- **Native Integration** — works with framework result handling
|
||||
- **Type Safety** — `Builders` serializes properly to `[]`; `List<Builder>` can cause casting issues
|
||||
|
||||
## Examples
|
||||
|
||||
### Serialize a Single Object
|
||||
```java
|
||||
import org.tinystruct.data.component.Builder;
|
||||
|
||||
Builder response = new Builder();
|
||||
response.put("status", "success");
|
||||
response.put("count", 42);
|
||||
return response.toString(); // {"status":"success","count":42}
|
||||
```
|
||||
|
||||
### Serialize a List using Builders
|
||||
```java
|
||||
import org.tinystruct.data.component.Builder;
|
||||
import org.tinystruct.data.component.Builders;
|
||||
|
||||
Builders dataList = new Builders();
|
||||
for (MyModel item : myCollection) {
|
||||
Builder b = new Builder();
|
||||
b.put("id", item.getId());
|
||||
b.put("name", item.getName());
|
||||
dataList.add(b);
|
||||
}
|
||||
Builder response = new Builder();
|
||||
response.put("data", dataList);
|
||||
return response.toString(); // {"data":[{"id":1,"name":"X"}]}
|
||||
```
|
||||
|
||||
### Parse a JSON Object
|
||||
```java
|
||||
Builder parsed = new Builder();
|
||||
parsed.parse(jsonString);
|
||||
String status = parsed.get("status").toString();
|
||||
```
|
||||
|
||||
### Parse a JSON Array
|
||||
```java
|
||||
Builders parsedArray = new Builders();
|
||||
parsedArray.parse(jsonArrayString);
|
||||
for (int i = 0; i < parsedArray.size(); i++) {
|
||||
Builder item = parsedArray.get(i);
|
||||
System.out.println(item.get("name"));
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
# tinystruct Database Persistence
|
||||
|
||||
## When to Use
|
||||
|
||||
Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending `AbstractData` and XML mapping files.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Architecture
|
||||
|
||||
Each table is represented by:
|
||||
1. **Java POJO**: Extends `AbstractData`, provides getters/setters and `setData(Row)`.
|
||||
2. **Mapping XML**: `ClassName.map.xml` in resources, binding Java fields to DB columns.
|
||||
|
||||
#### Key Base Class: `AbstractData`
|
||||
Provides CRUD methods:
|
||||
- `append()` / `appendAndGetId()`
|
||||
- `update()`
|
||||
- `delete()`
|
||||
- `findAll()` / `findOneById()` / `findOneByKey(key, value)`
|
||||
- `findWith(where, params)`
|
||||
- `find(SQL, params)`
|
||||
|
||||
### POJO Generation (CLI)
|
||||
|
||||
Introspect a live database table to produce the POJO and mapping file.
|
||||
|
||||
#### Configuration
|
||||
`application.properties`:
|
||||
```properties
|
||||
driver=com.mysql.cj.jdbc.Driver
|
||||
database.url=jdbc:mysql://localhost:3306/mydb
|
||||
database.user=root
|
||||
database.password=secret
|
||||
```
|
||||
|
||||
#### Command
|
||||
```bash
|
||||
# Interactive mode
|
||||
bin/dispatcher generate
|
||||
|
||||
# Specify table
|
||||
bin/dispatcher generate --tables users
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### CRUD Operations
|
||||
```java
|
||||
// CREATE
|
||||
User user = new User();
|
||||
user.setUsername("james");
|
||||
user.append();
|
||||
|
||||
// READ
|
||||
User user = new User();
|
||||
user.setId(42);
|
||||
user.findOneById();
|
||||
|
||||
// UPDATE
|
||||
user.setEmail("new@example.com");
|
||||
user.update();
|
||||
|
||||
// DELETE
|
||||
user.delete();
|
||||
```
|
||||
|
||||
### Querying with Conditions
|
||||
```java
|
||||
User user = new User();
|
||||
Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"});
|
||||
|
||||
// Fluent Condition Builder
|
||||
Condition condition = new Condition();
|
||||
condition.setRequestFields("id,username");
|
||||
Table filtered = user.find(
|
||||
condition.select("`users`").and("email LIKE ?").orderBy("id DESC"),
|
||||
new Object[]{"%@example.com"}
|
||||
);
|
||||
```
|
||||
|
||||
### Mapping XML Structure
|
||||
`User.map.xml`:
|
||||
```xml
|
||||
<mapping>
|
||||
<class name="User" table="users">
|
||||
<id name="Id" column="id" increment="true" generate="false" length="11" type="int"/>
|
||||
<property name="username" column="username" length="50" type="varchar"/>
|
||||
<property name="email" column="email" length="100" type="varchar"/>
|
||||
</class>
|
||||
</mapping>
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
1. **File Placement**: The mapping XML **must** mirror the POJO's package path under `src/main/resources/`.
|
||||
2. **Naming**: Table names are singularized for class names (`users` → `User`). Underscored columns become camelCase fields (`created_at` → `createdAt`).
|
||||
3. **Setters**: Use `setFieldAsXxx` methods (e.g., `setFieldAsString`) in setters to sync state with the internal field map.
|
||||
4. **Id Field**: The primary key field in Java is always named `Id` (inherited from `AbstractData`).
|
||||
@@ -0,0 +1,64 @@
|
||||
# tinystruct @Action Routing Reference
|
||||
|
||||
## When to Use
|
||||
|
||||
Use the `@Action` annotation in your applications to define routes for both CLI commands and HTTP endpoints. It is appropriate whenever you need to map logic to a specific path, handle parameterized requests, or restrict execution to specific HTTP methods while maintaining a consistent command structure across environments.
|
||||
|
||||
## How It Works
|
||||
|
||||
The `ActionRegistry` parses `@Action` annotations to build a routing table. For parameterized methods, the framework automatically maps Java parameter types to corresponding regex segments.
|
||||
|
||||
### Regex Generation Rules
|
||||
- `getUser(int id)` → pattern: `^/?user/(-?\d+)$`
|
||||
- `search(String query)` → pattern: `^/?search/([^/]+)$`
|
||||
|
||||
Supported parameter types: `String`, `int/Integer`, `long/Long`, `float/Float`, `double/Double`, `boolean/Boolean`, `char/Character`, `short/Short`, `byte/Byte`, `Date` (parsed as `yyyy-MM-dd HH:mm:ss`).
|
||||
|
||||
### Mode Values
|
||||
|
||||
| Mode | When it triggers |
|
||||
|---|---|
|
||||
| `DEFAULT` | Both CLI and HTTP (GET, POST, etc.) |
|
||||
| `CLI` | CLI dispatcher only |
|
||||
| `HTTP_GET` | HTTP GET only |
|
||||
| `HTTP_POST` | HTTP POST only |
|
||||
| `HTTP_PUT` | HTTP PUT only |
|
||||
| `HTTP_DELETE` | HTTP DELETE only |
|
||||
| `HTTP_PATCH` | HTTP PATCH only |
|
||||
|
||||
> **Note:** You can map HTTP method names to `Mode` using `Action.Mode.fromName(String methodName)`. Unknown or null values return `Mode.DEFAULT`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Action Declaration
|
||||
```java
|
||||
@Action(
|
||||
value = "path/subpath", // required: URI segment or CLI command
|
||||
description = "What it does", // shown in --help output
|
||||
mode = Mode.DEFAULT, // default: Mode.DEFAULT
|
||||
example = "bin/dispatcher path/subpath/42"
|
||||
)
|
||||
public String myAction(int id) { ... }
|
||||
```
|
||||
|
||||
### Parameterized Paths
|
||||
```java
|
||||
@Action("user/{id}")
|
||||
public String getUser(int id) { ... }
|
||||
// → CLI: bin/dispatcher user/42
|
||||
// → HTTP: /?q=user/42
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
`ActionRegistry` automatically injects `Request` and/or `Response` from `Context` if they are parameters:
|
||||
|
||||
```java
|
||||
@Action(value = "upload", mode = Mode.HTTP_POST)
|
||||
public String upload(Request<?, ?> req, Response<?, ?> res) throws ApplicationException {
|
||||
// Access raw request/response if needed
|
||||
return "ok";
|
||||
}
|
||||
```
|
||||
|
||||
### Path Matching Priority
|
||||
If two methods share the same path, the framework uses the first match in the `ActionRegistry`. Use explicit `Mode` values to disambiguate (e.g., separating a GET for a form and a POST for submission).
|
||||
@@ -0,0 +1,97 @@
|
||||
# tinystruct System and Usage Reference
|
||||
|
||||
## When to Use
|
||||
|
||||
Use these patterns to handle request state, manage web sessions, implement Server-Sent Events (SSE), handle file uploads, or perform outbound HTTP networking.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Context and CLI Arguments
|
||||
`Context` is the primary data store for request-specific state. CLI flags passed as `--key value` are stored in `Context` as `"--key"`.
|
||||
|
||||
### Session Management
|
||||
Pluggable architecture. Default is `MemorySessionRepository`. Configure Redis in `application.properties`:
|
||||
```properties
|
||||
default.session.repository=org.tinystruct.http.RedisSessionRepository
|
||||
redis.host=127.0.0.1
|
||||
redis.port=6379
|
||||
```
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
Built-in support for real-time push. The `HttpServer` automatically handles the SSE lifecycle when it detects the `Accept: text/event-stream` header. Connections are tracked by session ID in `SSEPushManager`.
|
||||
|
||||
### Outbound Networking
|
||||
Use `URLRequest` and `HTTPHandler` for making HTTP requests to external services.
|
||||
|
||||
## Examples
|
||||
|
||||
### Context and CLI Arguments
|
||||
```java
|
||||
@Action("echo")
|
||||
public String echo() {
|
||||
// CLI: bin/dispatcher echo --words "Hello World"
|
||||
Object words = getContext().getAttribute("--words");
|
||||
if (words != null) return words.toString();
|
||||
return "No words provided";
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management
|
||||
```java
|
||||
@Action(value = "login", mode = Mode.HTTP_POST)
|
||||
public String login(Request<?, ?> request) {
|
||||
request.getSession().setAttribute("userId", "42");
|
||||
return "Logged in";
|
||||
}
|
||||
```
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
```java
|
||||
@Action("sse/connect")
|
||||
public String connect() {
|
||||
return "{\"type\":\"connect\",\"message\":\"Connected\"}";
|
||||
}
|
||||
|
||||
// In another method or event handler:
|
||||
String sessionId = getContext().getId();
|
||||
SSEPushManager.getInstance().push(sessionId, new Builder().put("msg", "hello"));
|
||||
```
|
||||
|
||||
### File Uploads
|
||||
```java
|
||||
import org.tinystruct.data.FileEntity;
|
||||
|
||||
@Action(value = "upload", mode = Mode.HTTP_POST)
|
||||
public String upload(Request<?, ?> request) throws ApplicationException {
|
||||
List<FileEntity> files = request.getAttachments();
|
||||
if (files != null) {
|
||||
for (FileEntity file : files) {
|
||||
// file.getFilename(), file.getContent()
|
||||
}
|
||||
}
|
||||
return "Uploaded";
|
||||
}
|
||||
```
|
||||
|
||||
### Outbound HTTP
|
||||
```java
|
||||
import org.tinystruct.net.URLRequest;
|
||||
import org.tinystruct.net.handlers.HTTPHandler;
|
||||
|
||||
URLRequest request = new URLRequest(new URL("https://api.example.com"));
|
||||
request.setMethod("POST").setBody("{\"data\":\"val\"}");
|
||||
|
||||
HTTPHandler handler = new HTTPHandler();
|
||||
var response = handler.handleRequest(request);
|
||||
if (response.getStatusCode() == 200) {
|
||||
String body = response.getBody();
|
||||
}
|
||||
```
|
||||
|
||||
### Event System
|
||||
Register handlers in `init()` for asynchronous task execution.
|
||||
```java
|
||||
EventDispatcher.getInstance().registerHandler(MyEvent.class, event -> {
|
||||
CompletableFuture.runAsync(() -> doHeavyWork(event.getPayload()));
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,72 @@
|
||||
# tinystruct Testing Patterns
|
||||
|
||||
## When to Use
|
||||
|
||||
Use these patterns when writing unit tests for your applications with **JUnit 5**. Essential for verifying action logic, routing registration, and HTTP mode behavior.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Unit Testing Applications
|
||||
ActionRegistry is a singleton. To test an application:
|
||||
1. Instantiate the application.
|
||||
2. Provide a `Settings` object (triggers `init()` and annotation processing).
|
||||
3. Use `app.invoke(path, args)` to test logic directly.
|
||||
|
||||
### HTTP Integration Testing
|
||||
For tests involving the built-in HTTP server:
|
||||
1. Start `HttpServer` in a background thread.
|
||||
2. Use `ApplicationManager.call("start", context, Action.Mode.CLI)` to boot.
|
||||
3. Wait for the port to be open using a `Socket`.
|
||||
4. Use `URLRequest` and `HTTPHandler` to perform actual requests.
|
||||
|
||||
## Examples
|
||||
|
||||
### Unit Test
|
||||
```java
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.tinystruct.system.Settings;
|
||||
|
||||
class MyAppTest {
|
||||
private MyApp app;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
app = new MyApp();
|
||||
app.setConfiguration(new Settings());
|
||||
app.init(); // triggers @Action annotation processing and registers all actions
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHello() throws Exception {
|
||||
Object result = app.invoke("hello");
|
||||
Assertions.assertEquals("Hello!", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreet() throws Exception {
|
||||
Object result = app.invoke("greet", new Object[]{"James"});
|
||||
Assertions.assertEquals("Hello, James!", result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ActionRegistry Match Testing
|
||||
```java
|
||||
@Test
|
||||
void testRouting() {
|
||||
ActionRegistry registry = ActionRegistry.getInstance();
|
||||
Action action = registry.getAction("greet/James");
|
||||
Assertions.assertNotNull(action);
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Integration Pattern
|
||||
Reference: `src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java`
|
||||
|
||||
```java
|
||||
// Pattern:
|
||||
// 1. Start server in thread
|
||||
// 2. Poll for port availability
|
||||
// 3. Send HTTP request via HTTPHandler
|
||||
// 4. Assert response body/status
|
||||
```
|
||||
Reference in New Issue
Block a user