Forgejo and Gitea SDK wrappers, multi-repo git utilities, AgentCI dispatch, distributed job orchestrator, and data collection pipelines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
187 lines
5.3 KiB
Go
187 lines
5.3 KiB
Go
package collect
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"forge.lthn.ai/core/go/pkg/io"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestMarketCollector_Name_Good(t *testing.T) {
|
|
m := &MarketCollector{CoinID: "bitcoin"}
|
|
assert.Equal(t, "market:bitcoin", m.Name())
|
|
}
|
|
|
|
func TestMarketCollector_Collect_Bad_NoCoinID(t *testing.T) {
|
|
mock := io.NewMockMedium()
|
|
cfg := NewConfigWithMedium(mock, "/output")
|
|
|
|
m := &MarketCollector{}
|
|
_, err := m.Collect(context.Background(), cfg)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestMarketCollector_Collect_Good_DryRun(t *testing.T) {
|
|
mock := io.NewMockMedium()
|
|
cfg := NewConfigWithMedium(mock, "/output")
|
|
cfg.DryRun = true
|
|
|
|
m := &MarketCollector{CoinID: "bitcoin"}
|
|
result, err := m.Collect(context.Background(), cfg)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, result.Items)
|
|
}
|
|
|
|
func TestMarketCollector_Collect_Good_CurrentData(t *testing.T) {
|
|
// Set up a mock CoinGecko server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
data := coinData{
|
|
ID: "bitcoin",
|
|
Symbol: "btc",
|
|
Name: "Bitcoin",
|
|
MarketData: marketData{
|
|
CurrentPrice: map[string]float64{"usd": 42000.50},
|
|
MarketCap: map[string]float64{"usd": 800000000000},
|
|
TotalVolume: map[string]float64{"usd": 25000000000},
|
|
High24h: map[string]float64{"usd": 43000},
|
|
Low24h: map[string]float64{"usd": 41000},
|
|
PriceChange24h: 500.25,
|
|
PriceChangePct24h: 1.2,
|
|
MarketCapRank: 1,
|
|
CirculatingSupply: 19500000,
|
|
TotalSupply: 21000000,
|
|
LastUpdated: "2025-01-15T10:00:00Z",
|
|
},
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(data)
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Override base URL
|
|
oldURL := coinGeckoBaseURL
|
|
coinGeckoBaseURL = server.URL
|
|
defer func() { coinGeckoBaseURL = oldURL }()
|
|
|
|
mock := io.NewMockMedium()
|
|
cfg := NewConfigWithMedium(mock, "/output")
|
|
// Disable rate limiter to avoid delays in tests
|
|
cfg.Limiter = nil
|
|
|
|
m := &MarketCollector{CoinID: "bitcoin"}
|
|
result, err := m.Collect(context.Background(), cfg)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, result.Items) // current.json + summary.md
|
|
assert.Len(t, result.Files, 2)
|
|
|
|
// Verify current.json was written
|
|
content, err := mock.Read("/output/market/bitcoin/current.json")
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, content, "bitcoin")
|
|
|
|
// Verify summary.md was written
|
|
summary, err := mock.Read("/output/market/bitcoin/summary.md")
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, summary, "Bitcoin")
|
|
assert.Contains(t, summary, "42000.50")
|
|
}
|
|
|
|
func TestMarketCollector_Collect_Good_Historical(t *testing.T) {
|
|
callCount := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if callCount == 1 {
|
|
// Current data response
|
|
data := coinData{
|
|
ID: "ethereum",
|
|
Symbol: "eth",
|
|
Name: "Ethereum",
|
|
MarketData: marketData{
|
|
CurrentPrice: map[string]float64{"usd": 3000},
|
|
},
|
|
}
|
|
_ = json.NewEncoder(w).Encode(data)
|
|
} else {
|
|
// Historical data response
|
|
data := historicalData{
|
|
Prices: [][]float64{{1705305600000, 3000.0}, {1705392000000, 3100.0}},
|
|
MarketCaps: [][]float64{{1705305600000, 360000000000}},
|
|
TotalVolumes: [][]float64{{1705305600000, 15000000000}},
|
|
}
|
|
_ = json.NewEncoder(w).Encode(data)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
oldURL := coinGeckoBaseURL
|
|
coinGeckoBaseURL = server.URL
|
|
defer func() { coinGeckoBaseURL = oldURL }()
|
|
|
|
mock := io.NewMockMedium()
|
|
cfg := NewConfigWithMedium(mock, "/output")
|
|
cfg.Limiter = nil
|
|
|
|
m := &MarketCollector{CoinID: "ethereum", Historical: true}
|
|
result, err := m.Collect(context.Background(), cfg)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, result.Items) // current.json + summary.md + historical.json
|
|
assert.Len(t, result.Files, 3)
|
|
|
|
// Verify historical.json was written
|
|
content, err := mock.Read("/output/market/ethereum/historical.json")
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, content, "3000")
|
|
}
|
|
|
|
func TestFormatMarketSummary_Good(t *testing.T) {
|
|
data := &coinData{
|
|
Name: "Bitcoin",
|
|
Symbol: "btc",
|
|
MarketData: marketData{
|
|
CurrentPrice: map[string]float64{"usd": 50000},
|
|
MarketCap: map[string]float64{"usd": 1000000000000},
|
|
MarketCapRank: 1,
|
|
CirculatingSupply: 19500000,
|
|
TotalSupply: 21000000,
|
|
},
|
|
}
|
|
|
|
summary := FormatMarketSummary(data)
|
|
|
|
assert.Contains(t, summary, "# Bitcoin (BTC)")
|
|
assert.Contains(t, summary, "$50000.00")
|
|
assert.Contains(t, summary, "Market Cap Rank:** #1")
|
|
assert.Contains(t, summary, "Circulating Supply")
|
|
assert.Contains(t, summary, "Total Supply")
|
|
}
|
|
|
|
func TestMarketCollector_Collect_Bad_ServerError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer server.Close()
|
|
|
|
oldURL := coinGeckoBaseURL
|
|
coinGeckoBaseURL = server.URL
|
|
defer func() { coinGeckoBaseURL = oldURL }()
|
|
|
|
mock := io.NewMockMedium()
|
|
cfg := NewConfigWithMedium(mock, "/output")
|
|
cfg.Limiter = nil
|
|
|
|
m := &MarketCollector{CoinID: "bitcoin"}
|
|
result, err := m.Collect(context.Background(), cfg)
|
|
|
|
// Should have errors but not fail entirely
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, result.Errors)
|
|
}
|