picoclaw/pkg/miniapp/miniapp_test.go
dj-oyu bf29d4ca2e feat: add Telegram Mini App dashboard
- Single-file HTML Mini App with 4 tabs: Plan, Skills, Session, Config
- Plan tab shows phase/step list parsed from MEMORY.md with tap-to-complete
- Skills tab lists available skills with send bar for command construction
- Tailscale auto-detection for HTTPS hosting with TLS cert provisioning
- HMAC-SHA256 validation of Telegram initData for API authentication
- WebAppData handler in Telegram channel for command relay
- Menu Button registration for "Dashboard" in Telegram
- Health server extended with Mux() accessor and StartTLS support
- GetPlanPhases() as single source of truth for plan parsing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:22:41 +09:00

99 lines
2.6 KiB
Go

package miniapp
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"sort"
"strings"
"testing"
)
// buildInitData constructs a valid initData string from params and a bot token.
func buildInitData(params map[string]string, botToken string) string {
// Build data-check-string
var pairs []string
for k, v := range params {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(pairs)
dataCheckString := strings.Join(pairs, "\n")
// Compute secret key
secretKeyMac := hmac.New(sha256.New, []byte("WebAppData"))
secretKeyMac.Write([]byte(botToken))
secretKey := secretKeyMac.Sum(nil)
// Compute hash
hashMac := hmac.New(sha256.New, secretKey)
hashMac.Write([]byte(dataCheckString))
hash := hex.EncodeToString(hashMac.Sum(nil))
// Build query string
values := url.Values{}
for k, v := range params {
values.Set(k, v)
}
values.Set("hash", hash)
return values.Encode()
}
func TestValidateInitData(t *testing.T) {
botToken := "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
t.Run("valid initData", func(t *testing.T) {
params := map[string]string{
"query_id": "AAHdF6IQAAAAAN0XohDhrOrc",
"user": `{"id":279058397,"first_name":"Vlad"}`,
"auth_date": "1234567890",
}
initData := buildInitData(params, botToken)
if !ValidateInitData(initData, botToken) {
t.Error("ValidateInitData() returned false for valid data")
}
})
t.Run("tampered data", func(t *testing.T) {
params := map[string]string{
"query_id": "AAHdF6IQAAAAAN0XohDhrOrc",
"user": `{"id":279058397,"first_name":"Vlad"}`,
"auth_date": "1234567890",
}
initData := buildInitData(params, botToken)
// Tamper with the data
initData = strings.Replace(initData, "Vlad", "Evil", 1)
if ValidateInitData(initData, botToken) {
t.Error("ValidateInitData() returned true for tampered data")
}
})
t.Run("wrong bot token", func(t *testing.T) {
params := map[string]string{
"auth_date": "1234567890",
}
initData := buildInitData(params, botToken)
if ValidateInitData(initData, "wrong-token") {
t.Error("ValidateInitData() returned true for wrong bot token")
}
})
t.Run("missing hash", func(t *testing.T) {
if ValidateInitData("auth_date=1234567890", botToken) {
t.Error("ValidateInitData() returned true for missing hash")
}
})
t.Run("empty initData", func(t *testing.T) {
if ValidateInitData("", botToken) {
t.Error("ValidateInitData() returned true for empty initData")
}
})
t.Run("invalid query string", func(t *testing.T) {
if ValidateInitData("%%%invalid", botToken) {
t.Error("ValidateInitData() returned true for invalid query string")
}
})
}