File size: 3,937 Bytes
8d3471e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package admin

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"
	"time"

	"ds2api/internal/config"
)

func TestToAccountMissingFieldsRemainEmpty(t *testing.T) {
	acc := toAccount(map[string]any{
		"email":    "user@example.com",
		"password": "secret",
	})
	if acc.Email != "user@example.com" {
		t.Fatalf("unexpected email: %q", acc.Email)
	}
	if acc.Mobile != "" {
		t.Fatalf("expected empty mobile, got %q", acc.Mobile)
	}
	if acc.Token != "" {
		t.Fatalf("expected empty token, got %q", acc.Token)
	}
}

func TestFieldStringNilToEmpty(t *testing.T) {
	if got := fieldString(map[string]any{"token": nil}, "token"); got != "" {
		t.Fatalf("expected empty string for nil field, got %q", got)
	}
	if got := fieldString(map[string]any{}, "token"); got != "" {
		t.Fatalf("expected empty string for missing field, got %q", got)
	}
}

func TestMaskSecretPreviewKeepsOnlyFirstAndLastTwoChars(t *testing.T) {
	cases := map[string]string{
		"":         "",
		"a":        "*",
		"ab":       "**",
		"abcd":     "****",
		"abcdef":   "ab****ef",
		"abc12345": "ab****45",
	}

	for input, want := range cases {
		if got := maskSecretPreview(input); got != want {
			t.Fatalf("maskSecretPreview(%q)=%q want %q", input, got, want)
		}
	}
}

func TestGetConfigMasksAccountTokenPreview(t *testing.T) {
	h := newAdminTestHandler(t, `{
		"accounts":[{"email":"u@example.com","password":"pwd"}]
	}`)
	if err := h.Store.UpdateAccountToken("u@example.com", "abcdefgh"); err != nil {
		t.Fatalf("seed runtime token: %v", err)
	}

	req := httptest.NewRequest(http.MethodGet, "/admin/config", nil)
	rec := httptest.NewRecorder()
	h.getConfig(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("unexpected status: %d body=%s", rec.Code, rec.Body.String())
	}

	var payload map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	accounts, _ := payload["accounts"].([]any)
	if len(accounts) != 1 {
		t.Fatalf("expected 1 account, got %d", len(accounts))
	}
	first, _ := accounts[0].(map[string]any)
	if got, _ := first["token_preview"].(string); got != "ab****gh" {
		t.Fatalf("expected masked token preview, got %q", got)
	}
}

func TestRunAccountTestsConcurrentlyKeepsInputOrder(t *testing.T) {
	accounts := []config.Account{
		{Email: "a@example.com"},
		{Email: "b@example.com"},
		{Email: "c@example.com"},
	}
	results := runAccountTestsConcurrently(accounts, 2, func(idx int, acc config.Account) map[string]any {
		return map[string]any{
			"idx":     idx,
			"account": acc.Identifier(),
		}
	})
	if len(results) != len(accounts) {
		t.Fatalf("unexpected result length: got %d want %d", len(results), len(accounts))
	}
	for i := range accounts {
		gotIdx, _ := results[i]["idx"].(int)
		if gotIdx != i {
			t.Fatalf("result index mismatch at %d: got %d", i, gotIdx)
		}
		gotID, _ := results[i]["account"].(string)
		if gotID != accounts[i].Identifier() {
			t.Fatalf("result order mismatch at %d: got %q want %q", i, gotID, accounts[i].Identifier())
		}
	}
}

func TestRunAccountTestsConcurrentlyRespectsLimit(t *testing.T) {
	const limit = 3
	accounts := []config.Account{
		{Email: "1@example.com"},
		{Email: "2@example.com"},
		{Email: "3@example.com"},
		{Email: "4@example.com"},
		{Email: "5@example.com"},
		{Email: "6@example.com"},
	}
	var current int32
	var maxSeen int32
	_ = runAccountTestsConcurrently(accounts, limit, func(_ int, _ config.Account) map[string]any {
		c := atomic.AddInt32(&current, 1)
		for {
			m := atomic.LoadInt32(&maxSeen)
			if c <= m || atomic.CompareAndSwapInt32(&maxSeen, m, c) {
				break
			}
		}
		time.Sleep(20 * time.Millisecond)
		atomic.AddInt32(&current, -1)
		return map[string]any{"success": true}
	})
	if maxSeen > limit {
		t.Fatalf("concurrency exceeded limit: got %d > %d", maxSeen, limit)
	}
	if maxSeen < 2 {
		t.Fatalf("expected concurrent execution, max seen %d", maxSeen)
	}
}