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
99 changes: 89 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ jobs:
- name: Run go vet
run: go vet ./...

test:
name: Test
build:
name: Build Check
runs-on: ubuntu-latest
needs: [lint]
steps:
- uses: actions/checkout@v4

Expand All @@ -46,13 +47,20 @@ jobs:
go-version: '1.21'
cache: true

- name: Run tests
run: go test -v -race ./...
- name: Build
run: |
CGO_ENABLED=0 go build -o sekai-cli ./cmd/sekai-cli
./sekai-cli --help

build:
name: Build Check
integration-test:
name: Integration Tests
runs-on: ubuntu-latest
needs: [lint, test]
needs: [lint]
env:
CONTAINER: sekin-sekai-1
CHAIN_ID: testnet-1
SEKAI_CONTAINER: sekin-sekai-1
SEKAI_CHAIN_ID: testnet-1
steps:
- uses: actions/checkout@v4

Expand All @@ -62,7 +70,78 @@ jobs:
go-version: '1.21'
cache: true

- name: Build
- name: Build sekai-cli
run: CGO_ENABLED=0 go build -o sekai-cli ./cmd/sekai-cli

- name: Clone sekin repository
run: git clone --depth 1 https://github.com/kiracore/sekin.git /tmp/sekin

- name: Start SEKAI infrastructure
run: |
CGO_ENABLED=0 go build -o sekai-cli ./cmd/sekai-cli
./sekai-cli --help
cd /tmp/sekin
docker compose up -d sekai syslog-ng
echo "Waiting for containers to start..."
sleep 10
docker ps

- name: Initialize SEKAI network
run: |
# Run the spin-local-testnet script steps (except start which blocks)
echo ">>> Step 1: Initialize sekaid"
docker exec ${CONTAINER} /scaller init --chain-id "${CHAIN_ID}" --moniker "CI-Node"

echo ">>> Step 2: Add genesis key"
docker exec ${CONTAINER} /scaller keys-add --name genesis

echo ">>> Step 3: Add genesis account"
docker exec ${CONTAINER} /scaller add-genesis-account --name genesis --coins 300000000000000ukex

echo ">>> Step 4: Claim validator role"
docker exec ${CONTAINER} /scaller gentx-claim --name genesis --moniker "CI-Validator"

echo ">>> Step 5: Start sekaid (background with auto-restart)"
docker exec -d ${CONTAINER} /scaller start --restart always

echo "Waiting for node to start producing blocks..."
sleep 30

- name: Wait for chain to be ready
run: |
echo "Checking chain status..."
for i in {1..30}; do
if docker exec ${CONTAINER} /sekaid status 2>/dev/null | grep -q '"catching_up":false'; then
echo "Chain is ready and synced!"
docker exec ${CONTAINER} /sekaid status
exit 0
fi
echo "Attempt $i/30: Chain not ready yet, waiting..."
sleep 5
done
echo "Chain did not become ready in time"
docker exec ${CONTAINER} /sekaid status || true
exit 1

- name: Run sekai-cli status check
run: ./sekai-cli status

- name: Run scenario-based integration test
run: ./sekai-cli scenario run examples/scenarios/ci-integration-test.yaml

- name: Run Go integration tests
run: go test -v -timeout 30m ./test/integration/...

- name: Collect logs on failure
if: failure()
run: |
echo "=== Docker containers ==="
docker ps -a
echo "=== SEKAI container logs ==="
docker logs ${CONTAINER} 2>&1 | tail -200 || true
echo "=== Syslog logs ==="
docker logs sekin-syslog-ng-1 2>&1 | tail -100 || true

- name: Cleanup
if: always()
run: |
cd /tmp/sekin
docker compose down -v || true
78 changes: 78 additions & 0 deletions examples/scenarios/ci-integration-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# CI Integration Test Scenario
# This scenario tests basic sekai-cli functionality against a running SEKAI node
name: ci-integration-test
description: Integration tests for CI pipeline - verifies basic queries and transactions

steps:
# === Status Checks ===
- name: Check node status
module: status
action: status
output: node_status

- name: Get network properties
module: gov
action: network-properties
output: network_props

# === Key Management ===
- name: List keys
module: keys
action: list
output: keys_list

# === Bank Module Queries ===
- name: Check genesis balance
module: bank
action: balances
params:
address: genesis
output: genesis_balance

- name: Get total supply
module: bank
action: total-supply
output: total_supply

# === Governance Queries ===
- name: List all roles
module: gov
action: all-roles
output: all_roles

- name: Get genesis permissions
module: gov
action: permissions
params:
address: genesis
output: genesis_permissions

- name: List councilors
module: gov
action: councilors
output: councilors

# === Token Module Queries ===
- name: Get token rates
module: tokens
action: all-rates
output: token_rates

- name: Get token black whites
module: tokens
action: token-black-whites
output: token_black_whites

# === Staking Queries ===
- name: List validators
module: staking
action: validators
output: validators

# === Auth Module Queries ===
- name: Get genesis account info
module: auth
action: account
params:
address: genesis
output: genesis_account
69 changes: 56 additions & 13 deletions pkg/scenarios/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ func NewActionMapper(client sdk.Client) *ActionMapper {
// resolveAddress resolves a key name to an address if needed.
// If the input looks like a bech32 address (starts with "kira"), it's returned as-is.
// Otherwise, it tries to resolve it as a key name.
// TODO: Handle Ethereum addresses (0x...) for torii bridge integration.
// TODO: Handle custom bech32 prefixes for minted tokens.
func (m *ActionMapper) resolveAddress(ctx context.Context, nameOrAddress string) (string, error) {
// If it looks like an address, return as-is
if strings.HasPrefix(nameOrAddress, "kira") {
Expand Down Expand Up @@ -280,10 +282,14 @@ func (m *ActionMapper) executeKeys(ctx context.Context, action string, params ma
return nil, nil, err

case "parse":
address := params["address"]
if address == "" {
addrParam := params["address"]
if addrParam == "" {
return nil, nil, fmt.Errorf("keys.parse requires 'address' parameter")
}
address, err := m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
result, err := m.keysMod.Parse(ctx, address)
return result, nil, err

Expand Down Expand Up @@ -427,10 +433,14 @@ func (m *ActionMapper) executeAuth(ctx context.Context, action string, params ma

switch action {
case "account":
address := params["address"]
if address == "" {
addrParam := params["address"]
if addrParam == "" {
return nil, nil, fmt.Errorf("auth.account requires 'address' parameter")
}
address, err := m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
result, err := m.authMod.Account(ctx, address)
return result, nil, err

Expand Down Expand Up @@ -510,18 +520,26 @@ func (m *ActionMapper) executeGov(ctx context.Context, action string, params map
return result, nil, err

case "roles":
address := params["address"]
if address == "" {
addrParam := params["address"]
if addrParam == "" {
return nil, nil, fmt.Errorf("gov.roles requires 'address' parameter")
}
address, err := m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
result, err := m.govMod.Roles(ctx, address)
return result, nil, err

case "permissions":
address := params["address"]
if address == "" {
addrParam := params["address"]
if addrParam == "" {
return nil, nil, fmt.Errorf("gov.permissions requires 'address' parameter")
}
address, err := m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
result, err := m.govMod.Permissions(ctx, address)
return result, nil, err

Expand Down Expand Up @@ -586,13 +604,19 @@ func (m *ActionMapper) executeGov(ctx context.Context, action string, params map

case "role-assign", "assign-role":
from := params["from"]
address := params["address"]
addrParam := params["address"]
roleStr := params["role"]

if from == "" || address == "" || roleStr == "" {
if from == "" || addrParam == "" || roleStr == "" {
return nil, nil, fmt.Errorf("gov.role-assign requires 'from', 'address', and 'role' parameters")
}

// Resolve address (could be key name or actual address)
address, err := m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}

// Parse role ID as int
roleID, err := strconv.Atoi(roleStr)
if err != nil {
Expand Down Expand Up @@ -1489,8 +1513,17 @@ func (m *ActionMapper) executeStaking(ctx context.Context, action string, params

switch action {
case "validators":
// Resolve address if provided
var address string
if addrParam := params["address"]; addrParam != "" {
var err error
address, err = m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
}
opts := &staking.ValidatorQueryOpts{
Address: params["address"],
Address: address,
ValAddr: params["val_address"],
Moniker: params["moniker"],
Status: params["status"],
Expand All @@ -1499,14 +1532,24 @@ func (m *ActionMapper) executeStaking(ctx context.Context, action string, params
return result, nil, err

case "validator":
address := params["address"]
addrParam := params["address"]
valAddr := params["val_address"]
moniker := params["moniker"]

if address == "" && valAddr == "" && moniker == "" {
if addrParam == "" && valAddr == "" && moniker == "" {
return nil, nil, fmt.Errorf("staking.validator requires 'address', 'val_address', or 'moniker' parameter")
}

// Resolve address if provided
var address string
if addrParam != "" {
var err error
address, err = m.resolveAddress(ctx, addrParam)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve address: %w", err)
}
}

opts := &staking.ValidatorQueryOpts{
Address: address,
ValAddr: valAddr,
Expand Down