mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
- Add forex plugin (Frankfurter v2 API) with rate lookups, analysis, DM-based alerts, and daily cron poll. Backfills 1 year of history on startup for moving averages and buy signal scoring. - Fix bot hang caused by SQLite lock contention in reminder polling: rows cursor was held open while writing to the same DB. Collect results first, close cursor, then process. Same fix in milkcarton. - Add sync retry loop so the bot reconnects after network drops instead of silently exiting. StopSync() for clean Ctrl+C shutdown. - Add panic recovery to all dispatch, syncer, and cron paths. - Make all HTTP-calling plugin commands async (goroutines) so a slow or dead external API cannot block the message dispatch pipeline. Affects: lookup, stocks, forex, anime, movies, concerts, gaming, retro, wotd, urls, howami. - Extract DisplayName to Base, add db.Exec helper, convert silent error discards across the codebase. - Fix UNO mercy-kill bug (eliminated bot continues playing), adventure DM nag spam, stats column mismatch, per-call regex/replacer allocs. - Update README: forex commands, Finance section, 47 plugins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
149 lines
4.8 KiB
Go
149 lines
4.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestScoreGuess_ExactMatch(t *testing.T) {
|
|
results := scoreGuess("HELLO", "HELLO")
|
|
for i, r := range results {
|
|
if r != LetterCorrect {
|
|
t.Errorf("position %d: got %d, want LetterCorrect", i, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreGuess_AllAbsent(t *testing.T) {
|
|
results := scoreGuess("XXXYZ", "HELLO")
|
|
for i, r := range results {
|
|
if r != LetterAbsent {
|
|
t.Errorf("position %d: got %d, want LetterAbsent", i, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreGuess_MixedResults(t *testing.T) {
|
|
// WORLD vs HELLO:
|
|
// W=absent, O=present, R=absent, L=correct(pos3 matches), D=absent
|
|
results := scoreGuess("WORLD", "HELLO")
|
|
expected := []LetterResult{LetterAbsent, LetterPresent, LetterAbsent, LetterCorrect, LetterAbsent}
|
|
for i, r := range results {
|
|
if r != expected[i] {
|
|
t.Errorf("position %d: got %d, want %d", i, r, expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreGuess_DuplicateLetters(t *testing.T) {
|
|
// SPEED vs ABIDE:
|
|
// S=absent, P=absent, E=present(E is in ABIDE pos 4), E=present? no, only one E in answer
|
|
// Actually: ABIDE has E at position 4 (0-indexed: A=0,B=1,I=2,D=3,E=4)
|
|
// SPEED: S=absent, P=absent, E=absent(no more E after first match), E=present, D=present
|
|
results := scoreGuess("SPEED", "ABIDE")
|
|
// S not in ABIDE -> absent
|
|
// P not in ABIDE -> absent
|
|
// E in ABIDE pos4 -> present (consumes the E)
|
|
// E again but no more E in pool -> absent
|
|
// D in ABIDE pos3 -> present
|
|
expected := []LetterResult{LetterAbsent, LetterAbsent, LetterPresent, LetterAbsent, LetterPresent}
|
|
for i, r := range results {
|
|
if r != expected[i] {
|
|
t.Errorf("position %d: got %d, want %d", i, r, expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreGuess_DuplicateWithCorrect(t *testing.T) {
|
|
// LLAMA vs HELLO:
|
|
// L at 0: not correct (H), L is in HELLO -> present (consumes one L)
|
|
// L at 1: not correct (E), L is in HELLO -> present (consumes second L)
|
|
// A at 2: absent
|
|
// M at 3: absent
|
|
// A at 4: absent
|
|
// Wait, HELLO has L at positions 2,3. So pool after first pass has H,E,L,L,O
|
|
// Actually no first pass: no exact matches for LLAMA vs HELLO
|
|
// Pool = H,E,L,L,O
|
|
// L at 0: found L in pool, present (pool = H,E,L,O)
|
|
// L at 1: found L in pool, present (pool = H,E,O)
|
|
// A at 2: not found, absent
|
|
// M at 3: not found, absent
|
|
// A at 4: not found, absent
|
|
results := scoreGuess("LLAMA", "HELLO")
|
|
expected := []LetterResult{LetterPresent, LetterPresent, LetterAbsent, LetterAbsent, LetterAbsent}
|
|
for i, r := range results {
|
|
if r != expected[i] {
|
|
t.Errorf("position %d: got %d, want %d", i, r, expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreGuess_CorrectTakesPriority(t *testing.T) {
|
|
// HELLO vs LLAMA: H=absent, E=absent, L=correct(pos2? no)
|
|
// HELLO vs LEMON: H=absent, E=present, L=present, L=absent, O=present
|
|
// Let's use a clearer case:
|
|
// ALLAY vs LLAMA:
|
|
// A at 0: not L -> but A is in LLAMA. First pass: check exact matches
|
|
// pos0: A vs L -> no
|
|
// pos1: L vs L -> CORRECT
|
|
// pos2: L vs A -> no
|
|
// pos3: A vs M -> no
|
|
// pos4: Y vs A -> no
|
|
// Pool (unmatched answer letters): L(0), A(2), M(3), A(4)
|
|
// Second pass:
|
|
// pos0: A -> found A in pool, present (pool: L, M, A)
|
|
// pos2: L -> found L in pool, present (pool: M, A)
|
|
// pos3: A -> found A in pool, present (pool: M)
|
|
// pos4: Y -> not found, absent
|
|
results := scoreGuess("ALLAY", "LLAMA")
|
|
expected := []LetterResult{LetterPresent, LetterCorrect, LetterPresent, LetterPresent, LetterAbsent}
|
|
for i, r := range results {
|
|
if r != expected[i] {
|
|
t.Errorf("position %d: got %d, want %d", i, r, expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateLetterStates_Upgrades(t *testing.T) {
|
|
states := map[rune]LetterResult{}
|
|
|
|
// First guess: A is absent
|
|
updateLetterStates(states, "A", []LetterResult{LetterAbsent})
|
|
if states['A'] != LetterAbsent {
|
|
t.Errorf("A should be Absent, got %d", states['A'])
|
|
}
|
|
|
|
// Second guess: A is present — should upgrade
|
|
updateLetterStates(states, "A", []LetterResult{LetterPresent})
|
|
if states['A'] != LetterPresent {
|
|
t.Errorf("A should be Present after upgrade, got %d", states['A'])
|
|
}
|
|
|
|
// Third guess: A is correct — should upgrade again
|
|
updateLetterStates(states, "A", []LetterResult{LetterCorrect})
|
|
if states['A'] != LetterCorrect {
|
|
t.Errorf("A should be Correct after upgrade, got %d", states['A'])
|
|
}
|
|
}
|
|
|
|
func TestUpdateLetterStates_NoDowngrade(t *testing.T) {
|
|
states := map[rune]LetterResult{
|
|
'A': LetterCorrect,
|
|
}
|
|
|
|
// A was correct, now appears absent in a different position — should NOT downgrade
|
|
updateLetterStates(states, "A", []LetterResult{LetterAbsent})
|
|
if states['A'] != LetterCorrect {
|
|
t.Errorf("A should remain Correct, got %d", states['A'])
|
|
}
|
|
}
|
|
|
|
func TestWordleBasePots(t *testing.T) {
|
|
// Verify reward structure: fewer guesses = more money
|
|
for i := 1; i < len(wordleBasePots)-1; i++ {
|
|
if wordleBasePots[i] <= wordleBasePots[i+1] {
|
|
t.Errorf("pot for %d guesses (%d) should be > pot for %d guesses (%d)",
|
|
i, wordleBasePots[i], i+1, wordleBasePots[i+1])
|
|
}
|
|
}
|
|
}
|