feat: add IM admin management
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package imadmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testInviteListRequest struct {
|
||||
Page int `json:"page,omitempty"`
|
||||
PerPage int `json:"per_page,omitempty"`
|
||||
InviteCode string `json:"invite_code,omitempty"`
|
||||
BatchID int64 `json:"batch_id,omitempty"`
|
||||
}
|
||||
|
||||
func TestListCodesUsesSignedGetWithSnakeQuery(t *testing.T) {
|
||||
server := signedTestServer(t, http.MethodGet, "/admin/v1/im/registration-invite-codes", func(t *testing.T, r *http.Request, body []byte) {
|
||||
if len(body) != 0 {
|
||||
t.Fatalf("GET list should not send body, got %q", string(body))
|
||||
}
|
||||
query := r.URL.Query()
|
||||
if query.Get("page") != "2" || query.Get("per_page") != "20" || query.Get("batch_id") != "123" {
|
||||
t.Fatalf("unexpected query: %s", r.URL.RawQuery)
|
||||
}
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(Config{BaseUrl: server.URL, ServiceId: "hotgo-admin", ServiceSecret: "secret", Timeout: time.Second})
|
||||
var out Page[struct{}]
|
||||
err := client.ListCodes(context.Background(), AdminScope{TenantId: 9, OperatorId: 7, RequestId: "req-1"}, testInviteListRequest{Page: 2, PerPage: 20, BatchID: 123}, &out)
|
||||
if err != nil {
|
||||
t.Fatalf("ListCodes returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListCodesWithInviteCodeUsesSignedSearchPost(t *testing.T) {
|
||||
server := signedTestServer(t, http.MethodPost, "/admin/v1/im/registration-invite-codes/search", func(t *testing.T, r *http.Request, body []byte) {
|
||||
if !strings.Contains(string(body), `"invite_code":"Ab123"`) {
|
||||
t.Fatalf("POST search should send snake_case invite code, got %s", string(body))
|
||||
}
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(Config{BaseUrl: server.URL, ServiceId: "hotgo-admin", ServiceSecret: "secret", Timeout: time.Second})
|
||||
var out Page[struct{}]
|
||||
err := client.ListCodes(context.Background(), AdminScope{TenantId: 9, OperatorId: 7, RequestId: "req-2"}, testInviteListRequest{Page: 1, PerPage: 20, InviteCode: "Ab123"}, &out)
|
||||
if err != nil {
|
||||
t.Fatalf("ListCodes returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func signedTestServer(t *testing.T, method string, path string, inspect func(t *testing.T, r *http.Request, body []byte)) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != method {
|
||||
t.Fatalf("method = %s, want %s", r.Method, method)
|
||||
}
|
||||
if r.URL.Path != path {
|
||||
t.Fatalf("path = %s, want %s", r.URL.Path, path)
|
||||
}
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read body: %v", err)
|
||||
}
|
||||
inspect(t, r, body)
|
||||
assertSignedRequest(t, r, body, "secret")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"items":[],"page":1,"per_page":20,"total":0}}`))
|
||||
}))
|
||||
}
|
||||
|
||||
func assertSignedRequest(t *testing.T, r *http.Request, body []byte, secret string) {
|
||||
t.Helper()
|
||||
for _, name := range []string{"Authorization", "X-IM-Service-ID", "X-IM-Tenant-ID", "X-IM-Operator-ID", "X-IM-Timestamp", "X-IM-Nonce", "X-Request-ID"} {
|
||||
if r.Header.Get(name) == "" {
|
||||
t.Fatalf("missing header %s", name)
|
||||
}
|
||||
}
|
||||
if _, err := strconv.ParseInt(r.Header.Get("X-IM-Timestamp"), 10, 64); err != nil {
|
||||
t.Fatalf("timestamp is not unix seconds: %v", err)
|
||||
}
|
||||
bodyHash := sha256.Sum256(body)
|
||||
canonical := strings.Join([]string{
|
||||
r.Method,
|
||||
r.URL.RequestURI(),
|
||||
r.Header.Get("X-IM-Service-ID"),
|
||||
r.Header.Get("X-IM-Tenant-ID"),
|
||||
r.Header.Get("X-IM-Operator-ID"),
|
||||
r.Header.Get("X-IM-Timestamp"),
|
||||
r.Header.Get("X-IM-Nonce"),
|
||||
r.Header.Get("X-Request-ID"),
|
||||
hex.EncodeToString(bodyHash[:]),
|
||||
}, "\n")
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
_, _ = mac.Write([]byte(canonical))
|
||||
expected := fmt.Sprintf("IM-HMAC %s", hex.EncodeToString(mac.Sum(nil)))
|
||||
if r.Header.Get("Authorization") != expected {
|
||||
t.Fatalf("bad signature\nwant: %s\n got: %s", expected, r.Header.Get("Authorization"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user