package tmobile import ( "encoding/json" "fmt" "io" "net/http" "net/url" "reflect" "strings" "testing" "time" "fiskerinc.com/modules/grpc/sms" "fiskerinc.com/modules/tmobtokengen" ) type mockTG struct { wasSet chan tmobtokengen.EHTSMap } func (c *mockTG) Generate(ehts tmobtokengen.EHTSMap) (string, error) { c.wasSet <- ehts return "generatedPopToken", nil } func (c *mockTG) ClientSecretAsAuthVal() string { return "clientSecret" } type mockHTTPClientAT struct { wasSet chan *http.Request } func (c *mockHTTPClientAT) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockATjson)), }, nil } var mockATjson = `{ "id_token": "generatedToken", "access_token": "generatedToken", "expires_in": 3600, "token_type": "type", "resource": "https://api.mob.com/", "refresh_token": "refreshToken", "scope": "scope" }` var mockUrlStr = "https://api.mob.com" var mockUrl *url.URL func init() { mockUrl, _ = url.Parse(mockUrlStr) b, _ := json.Marshal(mockSmsDetailsResponse) mockSmsDetailsJsonRes = string(b) } func TestTMobClient_AccessToken(t *testing.T) { mHTTP := &mockHTTPClientAT{wasSet: make(chan *http.Request, 1)} mTG := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} cl := &TMobClient{ client: mHTTP, tg: mTG, baseURL: mockUrl, } atr, err := cl.AccessToken(mockCtx) if err != nil { t.Errorf("AccessToken() should have succeeded: %v", err) } if atr.AccessToken != "generatedToken" { t.Errorf("AccessToken() should have returned generated token") } select { case ehts := <-mTG.wasSet: if ehts["Authorization"] != "clientSecret" { t.Errorf("AccessToken() should have set Authorization header") } if ehts["uri"] != "/oauth2/v1/tokens" { t.Errorf("AccessToken() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("AccessToken() ehts should have set method") } if len(ehts) != 3 { t.Errorf("AccessToken() ehts should have set 3 keys") } case <-time.After(time.Second): t.Errorf("AccessToken() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/oauth2/v1/tokens" { t.Errorf("AccessToken() should have set URL") } if req.Method != "POST" { t.Errorf("AccessToken() should have set method") } if req.Header.Get("Authorization") != "clientSecret" { t.Errorf("AccessToken() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("AccessToken() should have set X-Authorization header") } if len(req.Header) != 2 { t.Errorf("AccessToken() should have set 2 headers") } if req.Body != nil { t.Errorf("AccessToken() should have no body") } case <-time.After(time.Second): t.Errorf("AccessToken() should have called http.Do()") } } func TestNewTMobileClient(t *testing.T) { cl, err := NewTMobileClient("http://localhost:8080", tmobtokengen.NewMockTokenGenerator{}, time.Second) if err != nil { t.Errorf("NewTMobileClient() should have succeeded: %v", err) } if cl == nil { t.Errorf("NewTMobileClient() should have returned non-nil") } cl, err = NewTMobileClient("\thisisnotparsable", nil, 0) if err == nil { t.Errorf("NewTMobileClient() should have failed: %v", err) } if cl != nil { t.Errorf("NewTMobileClient() should have returned nil") } } func TestTMobClient_SetAccessToken(t *testing.T) { cl := &TMobClient{} cl.SetAccessToken("token") if cl.accessToken != "Bearer token" { t.Errorf("SetAccessToken() should have set access token") } } type mockHTTPClientSendSMS struct { wasSet chan *http.Request } func (c *mockHTTPClientSendSMS) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockSendSMSJsonRes)), }, nil } var mockSendSMSJsonRes = `{"smsMessageId": "1"} ` func TestTMobClient_SendSMS(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientSendSMS{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } msrs := "{\"iccid\":\"12345678901234567890\",\"messageText\":\"Hello world\"}\n" res, err := cl.SendSMS(mockCtx, mockSmsRequest) if err != nil { t.Errorf("SendSMS() should have succeeded: %v", err) } if res.SmsMessageID != "1" { t.Errorf("SendSMS() should have returned smsMessageId") } select { case ehts := <-mtg.wasSet: if ehts["uri"] != "/eitcsr-iotcp-notifications-v2/prd02/iotcp/v2/notifications/sms/messages" { t.Errorf("SendSMS() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("SendSMS() ehts should have set method") } if ehts["Content-Type"] != "application/json" { t.Errorf("SendSMS() ehts should have set Content-Type") } if ehts["Authorization"] != "Bearer token" { t.Errorf("SendSMS() ehts should have set Authorization header") } if ehts["body"] != msrs { t.Errorf("SendSMS() ehts should have set Body") } if len(ehts) != 5 { t.Errorf("SendSMS() ehts should have set 4 keys") } case <-time.After(time.Second): t.Errorf("SendSMS() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/eitcsr-iotcp-notifications-v2/prd02/iotcp/v2/notifications/sms/messages" { t.Errorf("SendSMS() should have set URL") } if req.Method != "POST" { t.Errorf("SendSMS() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("SendSMS() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("SendSMS() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("SendSMS() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("SendSMS() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("SendSMS() should have set 4 headers") } b, err := io.ReadAll(req.Body) if err != nil { t.Errorf("SendSMS() should have read body") } if string(b) != msrs { t.Errorf("SendSMS() should have set body") } case <-time.After(time.Second): t.Errorf("SendSMS() should have called http.Do()") } } type mockHTTPClientDetails struct { wasSet chan *http.Request } func (c *mockHTTPClientDetails) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockSmsDetailsJsonRes)), }, nil } // inited above var mockSmsDetailsJsonRes string func TestTMobClient_Details(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientDetails{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } ID := "12345678901234567890" res, err := cl.Details(mockCtx, ID) if err != nil { t.Errorf("Details() should have succeeded: %v", err) } if !reflect.DeepEqual(mockSmsDetailsResponse, res) { t.Errorf("Details() got %v, want %v", res, mockSmsDetailsResponse) } genPath := fmt.Sprintf("/eitcsr-iotcp-notifications-v2/prd02/iotcp/v2/notifications/sms/messages/%s", ID) select { case ehts := <-mtg.wasSet: if ehts["uri"] != genPath { t.Errorf("Details() ehts should have set uri") } if ehts["http-method"] != "GET" { t.Errorf("Details() ehts should have set method") } if ehts["Authorization"] != "Bearer token" { t.Errorf("Details() ehts should have set Authorization header") } if len(ehts) != 3 { t.Errorf("Details() ehts should have set 3 keys") } case <-time.After(time.Second): t.Errorf("Details() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+genPath { t.Errorf("Details() should have set URL") } if req.Method != "GET" { t.Errorf("Details() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("Details() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("Details() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("Details() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("SendSMS() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("Details() should have set 4 headers") } if req.Body != nil { t.Errorf("Details() should have set nil body") } case <-time.After(time.Second): t.Errorf("Details() should have called http.Do()") } } func TestInitTokenGen(t *testing.T) { tg, err := InitTokenGen(mockPKCS8, "client_id", "secret") if err != nil { t.Errorf("InitTokenGen() error = %v", err) } if tg == nil { t.Errorf("InitTokenGen() tg is nil") } tg, err = InitTokenGen("/tmp/thisFileDoesntExist", "client_id", "secret") if err == nil { t.Errorf("InitTokenGen() should have failed") } if tg != nil { t.Errorf("InitTokenGen() should have returned nil") } tg, err = InitTokenGen(mockPKCS8Fail, "client_id", "secret") if err == nil { t.Errorf("InitTokenGen() should have failed") } if tg != nil { t.Errorf("InitTokenGen() should have returned nil") } } type mockHTTPClientCustomAttributes struct { wasSet chan *http.Request } var mockCustomAttributesRequest = &CustomAtributesRequest{ ICCID: "12345678901234567890", AccountCustom1: "US", } var mockCustomAttributesJsonRes = `{"iccid": "12345678901234567890"}` func (c *mockHTTPClientCustomAttributes) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockCustomAttributesJsonRes)), }, nil } func TestTMobClient_CustomAttributes(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientCustomAttributes{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } msrs := "{\"iccid\":\"12345678901234567890\",\"accountCustom1\":\"US\"}\n" res, err := cl.CustomAttributes(mockCtx, mockCustomAttributesRequest) if err != nil { t.Errorf("CustomAttributes() should have succeeded: %v", err) } if res.ICCID != "12345678901234567890" { t.Errorf("CustomAttributes() should have returned smsMessageId") } select { case ehts := <-mtg.wasSet: if ehts["uri"] != "/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/device-attributes" { t.Errorf("CustomAttributes() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("CustomAttributes() ehts should have set method") } if ehts["Content-Type"] != "application/json" { t.Errorf("CustomAttributes() ehts should have set Content-Type") } if ehts["Authorization"] != "Bearer token" { t.Errorf("CustomAttributes() ehts should have set Authorization header") } if ehts["body"] != msrs { t.Errorf("CustomAttributes() ehts should have set Body") } if len(ehts) != 5 { t.Errorf("CustomAttributes() ehts should have set 4 keys") } case <-time.After(time.Second): t.Errorf("CustomAttributes() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/device-attributes" { t.Errorf("CustomAttributes() should have set URL") } if req.Method != "POST" { t.Errorf("CustomAttributes() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("CustomAttributes() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("CustomAttributes() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("CustomAttributes() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("CustomAttributes() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("CustomAttributes() should have set 4 headers") } b, err := io.ReadAll(req.Body) if err != nil { t.Errorf("CustomAttributes() should have read body") } if string(b) != msrs { t.Errorf("CustomAttributes() should have set body") } case <-time.After(time.Second): t.Errorf("CustomAttributes() should have called http.Do()") } } type mockHTTPClientChangeRatePlan struct { wasSet chan *http.Request } var mockChangeRatePlanRequest = &ChangeRatePlanRequest{ ICCID: "12345678901234567890", ProductId: "product_1_with_rate_plan_1", } var mockChangeRatePlanRequestJsonRes = `{"iccid": "12345678901234567890"}` func (c *mockHTTPClientChangeRatePlan) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockChangeRatePlanRequestJsonRes)), }, nil } func TestTMobClient_ChangeRatePlan(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientChangeRatePlan{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } msrs := "{\"iccid\":\"12345678901234567890\",\"productId\":\"product_1_with_rate_plan_1\"}\n" res, err := cl.ChangeRatePlan(mockCtx, mockChangeRatePlanRequest) if err != nil { t.Errorf("ChangeRatePlan() should have succeeded: %v", err) } if res.ICCID != "12345678901234567890" { t.Errorf("ChangeRatePlan() should have returned smsMessageId") } select { case ehts := <-mtg.wasSet: if ehts["uri"] != "/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/change-rate-plan" { t.Errorf("ChangeRatePlan() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("ChangeRatePlan() ehts should have set method") } if ehts["Content-Type"] != "application/json" { t.Errorf("ChangeRatePlan() ehts should have set Content-Type") } if ehts["Authorization"] != "Bearer token" { t.Errorf("ChangeRatePlan() ehts should have set Authorization header") } if ehts["body"] != msrs { t.Errorf("ChangeRatePlan() ehts should have set Body") } if len(ehts) != 5 { t.Errorf("ChangeRatePlan() ehts should have set 4 keys") } case <-time.After(time.Second): t.Errorf("ChangeRatePlan() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/change-rate-plan" { t.Errorf("ChangeRatePlan() should have set URL") } if req.Method != "POST" { t.Errorf("ChangeRatePlan() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("ChangeRatePlan() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("ChangeRatePlan() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("ChangeRatePlan() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("ChangeRatePlan() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("ChangeRatePlan() should have set 4 headers") } b, err := io.ReadAll(req.Body) if err != nil { t.Errorf("ChangeRatePlan() should have read body") } if string(b) != msrs { t.Errorf("ChangeRatePlan() should have set body") } case <-time.After(time.Second): t.Errorf("ChangeRatePlan() should have called http.Do()") } } type mockHTTPClientGetProducts struct { wasSet chan *http.Request } var mockGetProductsRequest = &sms.GetAvailableProductsRequest{ AccountId: "fisker_tmobile_1", ProductType: []string{"RATEPLAN"}, ProductClassification: []string{"IOTCONNECTIVITY"}, } var mockGetProductsJsonRes = `[{"id": "1", "productId": "product_2","shortDescription": "short", "longDescription":"long", "status": "Published", "effectiveDate": "2021-02-17T19:49:00+00:00", "expirationDate": "", "ratePlan": {}}]` func (c *mockHTTPClientGetProducts) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockGetProductsJsonRes)), }, nil } func TestTMobClient_GetProducts(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientGetProducts{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } msrs := "{\"accountId\":\"fisker_tmobile_1\",\"productType\":[\"RATEPLAN\"],\"productClassification\":[\"IOTCONNECTIVITY\"]}\n" res, err := cl.GetProducts(mockCtx, mockGetProductsRequest) if err != nil { t.Errorf("GetProducts() should have succeeded: %v", err) } if res.AvailableProducts[0].ProductId != "product_2" { t.Errorf("GetProducts() should have returned correct products") } select { case ehts := <-mtg.wasSet: if ehts["uri"] != "/eitcsr-iot-product-service-v1/prd02/iotcp/v1/iot-product-service/products/query" { t.Errorf("GetProducts() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("GetProducts() ehts should have set method") } if ehts["Content-Type"] != "application/json" { t.Errorf("GetProducts() ehts should have set Content-Type") } if ehts["Authorization"] != "Bearer token" { t.Errorf("GetProducts() ehts should have set Authorization header") } if ehts["body"] != msrs { t.Errorf("GetProducts() ehts should have set Body") } if len(ehts) != 5 { t.Errorf("GetProducts() ehts should have set 4 keys") } case <-time.After(time.Second): t.Errorf("GetProducts() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/eitcsr-iot-product-service-v1/prd02/iotcp/v1/iot-product-service/products/query" { t.Errorf("GetProducts() should have set URL") } if req.Method != "POST" { t.Errorf("GetProducts() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("GetProducts() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("GetProducts() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("GetProducts() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("GetProducts() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("GetProducts() should have set 4 headers") } b, err := io.ReadAll(req.Body) if err != nil { t.Errorf("GetProducts() should have read body") } if string(b) != msrs { t.Errorf("GetProducts() should have set body") } case <-time.After(time.Second): t.Errorf("GetProducts() should have called http.Do()") } } type mockHTTPClientDeviceDetails struct { wasSet chan *http.Request } var mockDeviceDetailsRequest = &DeviceDetailsRequest{ ICCID: "1234567890", } var mockDeviceDetailsJsonRes = `{"iccid": "1234567890", "ratePlan": "plan_1", "accountId": "account_1", "accountCustom1": "US", "status": "published"}` func (c *mockHTTPClientDeviceDetails) Do(req *http.Request) (*http.Response, error) { c.wasSet <- req return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockDeviceDetailsJsonRes)), }, nil } func TestTMobClient_DeviceDetails(t *testing.T) { mtg := &mockTG{wasSet: make(chan tmobtokengen.EHTSMap, 1)} mHTTP := &mockHTTPClientDeviceDetails{wasSet: make(chan *http.Request, 1)} cl := &TMobClient{ client: mHTTP, tg: mtg, baseURL: mockUrl, accessToken: "Bearer token", } msrs := "{\"iccid\":\"1234567890\"}\n" res, err := cl.DeviceDetails(mockCtx, mockDeviceDetailsRequest) if err != nil { t.Errorf("DeviceDetails() should have succeeded: %v", err) } if res.ICCID != "1234567890" { t.Errorf("DeviceDetails() should have returned correct products") } select { case ehts := <-mtg.wasSet: if ehts["uri"] != "/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/details" { t.Errorf("DeviceDetails() ehts should have set uri") } if ehts["http-method"] != "POST" { t.Errorf("DeviceDetails() ehts should have set method") } if ehts["Content-Type"] != "application/json" { t.Errorf("DeviceDetails() ehts should have set Content-Type") } if ehts["Authorization"] != "Bearer token" { t.Errorf("DeviceDetails() ehts should have set Authorization header") } if ehts["body"] != msrs { t.Errorf("DeviceDetails() ehts should have set Body") } if len(ehts) != 5 { t.Errorf("DeviceDetails() ehts should have set 4 keys") } case <-time.After(time.Second): t.Errorf("DeviceDetails() should have called tg.Generate()") } select { case req := <-mHTTP.wasSet: if req.URL.String() != mockUrlStr+"/eitcsr-iotcp-line-of-service-v1/prd02/iotcp/v1/line-of-service/devices/details" { t.Errorf("DeviceDetails() should have set URL") } if req.Method != "POST" { t.Errorf("DeviceDetails() should have set method") } if req.Header.Get("Authorization") != "Bearer token" { t.Errorf("DeviceDetails() should have set Authorization header") } if req.Header.Get("X-Authorization") != "generatedPopToken" { t.Errorf("DeviceDetails() should have set X-Authorization header") } if req.Header.Get(XAuthOriginator) != "token" { t.Errorf("DeviceDetails() should have set x-auth-originator header") } if req.Header.Get("Content-Type") != "application/json" { t.Errorf("DeviceDetails() should have set X-Authorization header") } if len(req.Header) != 4 { t.Errorf("DeviceDetails() should have set 4 headers") } b, err := io.ReadAll(req.Body) if err != nil { t.Errorf("DeviceDetails() should have read body") } if string(b) != msrs { t.Errorf("DeviceDetails() should have set body") } case <-time.After(time.Second): t.Errorf("DeviceDetails() should have called http.Do()") } }