package loldle
import (
"context"
"encoding/json"
"fmt"
"math"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"strings"
"testing"
"time"
"github.com/tiennm99/tiennm99bot/internal/modules/loldle/web"
"github.com/tiennm99/tiennm99bot/internal/modules/util/guessgame"
)
func urlUnescape(t *testing.T, s string) string {
t.Helper()
u, err := url.QueryUnescape(s)
if err != nil {
t.Fatal(err)
}
return u
}
func TestRules_MarksArrowsAndRefusals(t *testing.T) {
r := rules{champions: loadChampions()}
for guess, want := range map[string]string{"Akali": "cwwwccu", "wukong": "wcwccwc", "kaisa": "cwccwwd", "Ahri": "ccccccc"} {
g, err := r.Judge(guess, "Ahri", nil)
if err != nil || g.Marks != want {
t.Errorf("%s vs Ahri = %+v, %v; want %s", guess, g, err, want)
}
}
if g, _ := r.Judge("jinx", "Kai'Sa", nil); g.Word != "Jinx" || g.Marks != "cpccwcu" {
t.Errorf("Jinx vs Kai'Sa = %+v", g)
}
for input, want := range map[string]error{"zzz": errUnknownChampion, "ka": errAmbiguous, "": errUnknownChampion} {
if _, err := r.Judge(input, "Ahri", nil); err != want {
t.Errorf("Judge(%q) = %v, want %v", input, err, want)
}
}
if _, err := r.Judge("akali", "Ahri", []guessgame.Guess{{Word: "Akali"}}); err != errDuplicate {
t.Errorf("duplicate = %v", err)
}
if _, err := r.Judge("akali", "Gone", nil); err != errTargetGone {
t.Errorf("gone = %v", err)
}
if got := guessgame.EmojiRow(r, "cpwudcw"); got != "๐ฉ๐จ๐ฅ๐ฅ๐ฅ๐ฉ๐ฅ" {
t.Errorf("emoji = %q", got)
}
}
func TestChampions_EveryRowHasAnIconID(t *testing.T) {
seen := map[string]bool{}
idRe := regexp.MustCompile(`^[A-Za-z]+$`)
for _, c := range loadChampions() {
if !idRe.MatchString(c.ID) || seen[c.ID] || c.Title == "" {
t.Errorf("%s: id %q title %q", c.ChampionName, c.ID, c.Title)
}
seen[c.ID] = true
}
}
// Data Dragon tile file names are case-sensitive and do not always match the
// champion key: Fiddlesticks' tile is FiddleSticks_0.jpg.
func TestChampions_IconIDsNameTheTileFile(t *testing.T) {
ids := map[string]string{}
for _, c := range loadChampions() {
ids[c.ChampionName] = c.ID
}
for name, want := range map[string]string{"Fiddlesticks": "FiddleSticks", "Wukong": "MonkeyKing", "Ahri": "Ahri"} {
if ids[name] != want {
t.Errorf("%s icon id = %q, want %q", name, ids[name], want)
}
}
}
func TestAPI_ChampionListCarriesNamesAndIDsOnly(t *testing.T) {
h := newHarness(t)
rec := httptest.NewRecorder()
h.handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, routePrefix+"champions.json", nil))
if rec.Code != http.StatusOK || !strings.HasPrefix(rec.Header().Get("Content-Type"), "application/json") {
t.Fatalf("champions.json: %d %v", rec.Code, rec.Header())
}
var list []map[string]string
if err := json.Unmarshal(rec.Body.Bytes(), &list); err != nil || len(list) != len(loadChampions()) {
t.Fatalf("list = %d, %v", len(list), err)
}
for _, c := range list {
if len(c) != 2 || c["name"] == "" || c["id"] == "" {
t.Fatalf("entry %v", c)
}
}
if list[0]["name"] != "Aatrox" || !strings.Contains(rec.Body.String(), `{"name":"Wukong","id":"MonkeyKing"}`) {
t.Fatalf("list starts %v", list[0])
}
}
func TestAPI_HeadersAllowDataDragonImagesOnly(t *testing.T) {
h := newHarness(t)
for path, ctype := range map[string]string{"": "text/html", "app.js": "text/javascript", "app.css": "text/css"} {
rec := httptest.NewRecorder()
h.handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, routePrefix+path, nil))
csp := rec.Header().Get("Content-Security-Policy")
if rec.Code != http.StatusOK || !strings.HasPrefix(rec.Header().Get("Content-Type"), ctype) ||
!strings.Contains(csp, "img-src 'self' data: https://ddragon.leagueoflegends.com;") || !strings.Contains(csp, "connect-src 'self'") {
t.Errorf("GET %q: %d %v", path, rec.Code, rec.Header())
}
}
if r := h.state("garbage"); r.code != http.StatusUnauthorized || r.err != "bad_token" {
t.Fatalf("bad token = %d %s", r.code, r.body)
}
}
var (
inlineScriptRe = regexp.MustCompile(`(?is)`)
urlRe = regexp.MustCompile(`https?://[^'"\s)]+`)
inlineHandlerRe = regexp.MustCompile(`\son[a-z]+=`)
)
// The page runs under the strict CSP: no inline script, style or handler,
// and it reaches no host but its own and Data Dragon's icons.
func TestWebAssets_FitTheCSP(t *testing.T) {
index, _ := web.FS.ReadFile("index.html")
for _, m := range inlineScriptRe.FindAllStringSubmatch(string(index), -1) {
if strings.TrimSpace(m[2]) != "" || !strings.Contains(m[1], "src=") {
t.Errorf("inline script breaks the CSP: %q", m[0])
}
}
if strings.Contains(string(index), " style=") || strings.Contains(string(index), "