chore: 并入 iotlcm-upstream-chan 历史到 upstream-chan/(原提交 1e74497)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:02 +08:00
192 changed files with 33119 additions and 0 deletions
@@ -0,0 +1,196 @@
package upstreamimpl
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/service"
)
// yunzeExpire renders a wall-clock expiry in the operator-token wire format
// (Asia/Shanghai, "2006-01-02 15:04:05").
func yunzeExpire() string {
return time.Now().In(yunzeTokenLocation).Add(10 * time.Minute).Format(yunzeTokenExpiryLayout)
}
func TestYunzeTokenProviderFetch(t *testing.T) {
expire := time.Now().In(yunzeTokenLocation).Add(10 * time.Minute)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("method = %s, want POST", r.Method)
}
if r.URL.Path != "/openapi/v3/query/operator-token" {
t.Fatalf("path = %s", r.URL.Path)
}
if got := r.Header.Get("Content-Type"); got != "application/json" {
t.Fatalf("content type = %q", got)
}
var request yunzeTokenRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.AppID != "app-id" || request.ChanKey != "249" {
t.Fatalf("request = %+v", request)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code":0,"message":"","data":{"chanAlias":"中国移动物联网","chanKey":"249","accessToken":"token-1","tokenExpire":"` + expire.Format(yunzeTokenExpiryLayout) + `"},"error":null}`))
}))
defer server.Close()
provider := NewYunzeTokenProvider(YunzeTokenConfig{
BaseURL: server.URL,
Path: "/openapi/v3/query/operator-token",
AppID: "app-id",
Timeout: time.Second,
})
token, err := provider.Fetch(context.Background(), "249")
if err != nil {
t.Fatalf("fetch token: %v", err)
}
if token.Value != "token-1" {
t.Fatalf("token = %q", token.Value)
}
if token.ExpiresAt.IsZero() {
t.Fatal("token expiry is empty")
}
if token.ExpiresAt.Sub(expire).Abs() > time.Second {
t.Fatalf("expiry = %s, want %s", token.ExpiresAt, expire)
}
}
// Pins the wire field name to the live endpoint. The published documentation
// lists data.accessTokenExpire, but the endpoint returns data.tokenExpire; if
// either side changes, this test fails and forces a deliberate decision.
func TestYunzeTokenProviderExpiryFieldName(t *testing.T) {
expire := yunzeExpire()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"code":0,"data":{"accessToken":"token-1","tokenExpire":"` + expire + `"},"error":null}`))
}))
defer server.Close()
provider := NewYunzeTokenProvider(YunzeTokenConfig{BaseURL: server.URL, AppID: "app-id", Timeout: time.Second})
token, err := provider.Fetch(context.Background(), "249")
if err != nil {
t.Fatalf("fetch token: %v", err)
}
if token.Value != "token-1" || token.ExpiresAt.IsZero() {
t.Fatalf("token = %+v", token)
}
}
func TestYunzeTokenProviderAcceptsStringCode(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"code":"0","data":{"accessToken":"token-str","tokenExpire":"` + yunzeExpire() + `"},"error":null}`))
}))
defer server.Close()
provider := NewYunzeTokenProvider(YunzeTokenConfig{BaseURL: server.URL, AppID: "app-id", Timeout: time.Second})
token, err := provider.Fetch(context.Background(), "249")
if err != nil {
t.Fatalf("fetch token: %v", err)
}
if token.Value != "token-str" {
t.Fatalf("token = %q, want token-str", token.Value)
}
}
func TestYunzeTokenProviderRejectsFailedResponse(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"code":1001,"message":"通道不存在","data":null,"error":null}`))
}))
defer server.Close()
provider := NewYunzeTokenProvider(YunzeTokenConfig{BaseURL: server.URL, AppID: "app-id", Timeout: time.Second})
if _, err := provider.Fetch(context.Background(), "249"); err == nil {
t.Fatal("expected failed response error")
}
}
func TestYunzeTokenProviderRejectsErrorPayload(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"code":0,"data":{"accessToken":"token-1","tokenExpire":"` + yunzeExpire() + `"},"error":{"msg":"account frozen"}}`))
}))
defer server.Close()
provider := NewYunzeTokenProvider(YunzeTokenConfig{BaseURL: server.URL, AppID: "app-id", Timeout: time.Second})
if _, err := provider.Fetch(context.Background(), "249"); err == nil {
t.Fatal("expected error payload rejection")
}
}
func TestParseSharedOneLinkConfigBusinessURL(t *testing.T) {
newConfig := func(tempConfig string) *model.SimChannelConfigItemCache {
return &model.SimChannelConfigItemCache{
Template: &entity.TSimChannelTemplate{TempConfig: tempConfig},
Channel: &entity.TSimChannels{
ChanKey: "249",
AppKey: "app-id",
ChanConfig: "{}",
},
}
}
t.Run("defaults to shared OneLink URL", func(t *testing.T) {
config, api, err := parseSharedOneLinkConfig(context.Background(), newConfig(
`{"token":{"provider":"yunze","baseUrl":"http://apiv3.example.com","path":"/openapi/v3/query/operator-token"}}`,
))
if err != nil {
t.Fatalf("parse config: %v", err)
}
if config.Provider != "yunze" || config.AppID != "app-id" {
t.Fatalf("config = %+v", config)
}
if api.BaseURL != sharedOneLinkAPIBaseURL {
t.Fatalf("business base URL = %q, want %q", api.BaseURL, sharedOneLinkAPIBaseURL)
}
})
t.Run("honours template api.baseUrl", func(t *testing.T) {
const custom = "https://custom.example/v5/"
config, api, err := parseSharedOneLinkConfig(context.Background(), newConfig(
`{"api":{"baseUrl":"`+custom+`"},"token":{"provider":"yunze","baseUrl":"http://apiv3.example.com","path":"/openapi/v3/query/operator-token"}}`,
))
if err != nil {
t.Fatalf("parse config: %v", err)
}
if config.Provider != "yunze" || config.AppID != "app-id" {
t.Fatalf("config = %+v", config)
}
if api.BaseURL != custom {
t.Fatalf("business base URL = %q, want %q", api.BaseURL, custom)
}
})
}
// The yunze channel template is registered by its concrete temp_key, matching
// the other shared-token templates (sim_onelink_<provider>_v1).
func TestYunzeTemplateRegistration(t *testing.T) {
template, ok := service.UpstreamAPI().GetTemp(sharedOneLinkYunzeTemplateKey)
if !ok || template == nil {
t.Fatal("yunze template not registered")
}
config := &model.SimChannelConfigItemCache{
Template: &entity.TSimChannelTemplate{
TempKey: sharedOneLinkYunzeTemplateKey,
TempModel: templateModelSharedToken,
TempConfig: `{"token":{"provider":"yunze","baseUrl":"http://apiv3.example.com","path":"/openapi/v3/query/operator-token"}}`,
},
Channel: &entity.TSimChannels{
ChanKey: "249",
AppKey: "YZTKB3FT1SJR8652C4WP9VM",
ChanConfig: "{}",
},
}
if instance := template.NewTemp(context.Background(), config); instance == nil {
t.Fatal("yunze template failed to instantiate")
}
}