package tmobtokengen import ( _ "embed" "log" "os" "reflect" "runtime" "testing" "time" "github.com/go-jose/go-jose/v4" ) var mockPrivateKey any //go:embed pkcs8_test.key var mockPrivateKeyStr []byte func mockUuidFunc() string { return "b04b038e-52f0-b7d0-95f9-1cb04475f2ab" } var mockClientSecret = "dGVzdDp0ZXN0" // test:test var mockEhtsMapEmptyBody = EHTSMap{}. SetAuthorization("Basic " + mockClientSecret). SetURI("/oauth2/v6/tokens"). SetHTTPMethod("POST") var mockEhtsMapNonEmptyBody = EHTSMap{}. SetAuthorization("Basic " + mockClientSecret). SetURI("/iotcp/v1/line-of-service/devices/summary"). SetHTTPMethod("POST"). SetContentType("application/json"). SetBody(`{"modifiedSince": "2021-02-17T00:00:00+00:00", "accountId" : "12342"}`) var mockTime = time.Unix(1656322028, 179000000).In(time.UTC) var mockSigner jose.Signer func TestMain(m *testing.M) { var err error //let's set mock private key mockPrivateKey, err = ParsePrivateKey(mockPrivateKeyStr, nil) if err != nil { log.Printf("error parsing mockPrivateKey: %v", err) os.Exit(1) } opts := new(jose.SignerOptions) opts.WithType("JWT") //let's set mock signer mockSigner, err = jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: mockPrivateKey}, opts) if err != nil { log.Printf("error creating mockSigner: %v", err) os.Exit(1) } m.Run() } func TestGenerateUUID(t *testing.T) { got1 := GenerateUUID() got2 := GenerateUUID() if got1 == "" || got2 == "" || got1 == got2 { t.Errorf("GenerateUUD() something very weird is going on: got1 = %v, got2 = %v", got1, got2) } } func TestNewTokenGenerator(t *testing.T) { b, err := os.ReadFile("./pkcs8_test.key") if err != nil { t.Fatalf("Missing test file to load %v", err) } tg, err := NewTokenGenerator("test", "test", time.Minute, string(b)) if err != nil { t.Fatalf("NewTokenGenerator() error = %v", err) } if tg == nil { t.Fatalf("NewTokenGenerator() = %v, want non-nil", tg) } if tg.signer == nil { t.Fatalf("NewTokenGenerator() signer = %v, want non-nil", tg.signer) } opts := tg.signer.Options() if opts.ExtraHeaders == nil { t.Fatalf("NewTokenGenerator() opts() = %v, want non-nil", opts) } if len(opts.ExtraHeaders) != 1 { t.Fatalf("NewTokenGenerator() len(opts.ExtraHeaders) = %v, want 1", len(opts.ExtraHeaders)) } if val, ok := opts.ExtraHeaders["typ"]; !ok || val != jose.ContentType("JWT") { t.Fatalf("NewTokenGenerator() signer.Options() typ = %v, want JWT", opts.ExtraHeaders["typ"]) } if tg.clientSecret != mockClientSecret { t.Fatalf("NewTokenGenerator() clientSecret = %v, want %v", tg.clientSecret, mockClientSecret) } if tg.expDuration != time.Minute { t.Fatalf("NewTokenGenerator() expDuration = %v, want %v", tg.expDuration, time.Minute) } f1 := runtime.FuncForPC(reflect.ValueOf(tg.genUuid).Pointer()).Name() f2 := runtime.FuncForPC(reflect.ValueOf(GenerateUUID).Pointer()).Name() if f1 != f2 { t.Errorf("NewTokenGenerator() genUuid = %s, want %s", f1, f2) } } func TestParsePrivateKey(t *testing.T) { key, err := ParsePrivateKey(mockPrivateKeyStr, nil) if err != nil { t.Fatalf("ParsePrivateKey() got err = %v, want nil", err) } if key == nil { t.Fatalf("ParsePrivateKey() got key = %v, want non-nil", key) } key, err = ParsePrivateKey([]byte{}, nil) if err == nil { t.Fatalf("ParsePrivateKey() got err = %v, want non-nil", err) } if key != nil { t.Fatalf("ParsePrivateKey() got key = %v, want nil", key) } } func TestPopTokenGenerator_ClientSecretAsAuthVal(t *testing.T) { g := &PopTokenGenerator{ signer: mockSigner, clientSecret: mockClientSecret, expDuration: time.Minute, genUuid: mockUuidFunc, } authVal := "Basic " + mockClientSecret if got := g.ClientSecretAsAuthVal(); got != authVal { t.Errorf("ClientSecretAsAuthVal() got = %v, want = %v", got, authVal) } } func TestPopTokenGenerator_Generate(t *testing.T) { g := &PopTokenGenerator{ signer: mockSigner, clientSecret: mockClientSecret, expDuration: time.Minute * 2, genUuid: mockUuidFunc, } got, err := g.Generate(mockEhtsMapEmptyBody) if err != nil { t.Fatalf("Generate() error = %v", err) } if len(got) == 0 { t.Fatalf("Generate() got = %v, want non-empty", got) } } func TestPopTokenGenerator_buildClaims(t *testing.T) { c, err := buildClaims(mockEhtsMapEmptyBody, mockTime, time.Minute, mockUuidFunc) if err != nil { t.Fatalf("buildClaims() got err = %v, want nil", err) } builtClaims := "{\"edts\":\"wdFcplwLzHDuO4_OlXLFrvh28DwtgKswIrnsUj0dU0I\",\"v\":\"1\",\"exp\":1656322088,\"ehts\":\"Authorization;uri;http-method\",\"iat\":1656322028,\"jti\":\"b04b038e-52f0-b7d0-95f9-1cb04475f2ab\"}" if string(c) != builtClaims { t.Errorf("buildClaims() got = %v, want = %v", string(c), builtClaims) } } func TestPopTokenGenerator_generate(t *testing.T) { g := &PopTokenGenerator{ signer: mockSigner, clientSecret: mockClientSecret, expDuration: time.Minute * 2, genUuid: mockUuidFunc, } type args struct { ehts EHTSMap curTime time.Time } tests := []struct { name string args args want string wantErr bool }{ { name: "success-empty-body", args: args{ ehts: mockEhtsMapEmptyBody, curTime: mockTime, }, want: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlZHRzIjoid2RGY3Bsd0x" + "6SER1TzRfT2xYTEZydmgyOER3dGdLc3dJcm5zVWowZFUwSSIsInYiOiIxIiwiZ" + "XhwIjoxNjU2MzIyMTQ4LCJlaHRzIjoiQXV0aG9yaXphdGlvbjt1cmk7aHR0cC1" + "tZXRob2QiLCJpYXQiOjE2NTYzMjIwMjgsImp0aSI6ImIwNGIwMzhlLTUyZjAtY" + "jdkMC05NWY5LTFjYjA0NDc1ZjJhYiJ9.Pe4BLC1LeClMzJ4UdZXN3CVT-eG52i" + "60RsGH70RsXquy4rRDV0IxE1f7Wr04nGT9t1YJXG4qBaiX3VDrqvk03f7Acn0Q" + "wyRQCItDiUiMHWNAB3FwkAllyJIyuT6l9IQehTWC0YT4Fv0HF0K5XUlt8sIp63" + "Lk0HU-iibUzkfN7FSvXovZz1uy4zLD6bbodxFwYs4HOo6tPiVkapLuJlET3mez" + "__m8b-qeQzcZ45sNOIL6MQ-UZDB8LNFUJOr4Wdq6ox3QM8owaXoRVf9ffkAFmT" + "X4kNg2knz8CLVWMtVzgPOQX7s7qoTVCDucz3Yxx-1hN1HUu1Kgjhau2G-DDq9i" + "mA", wantErr: false, }, { name: "success-with-body", args: args{ ehts: mockEhtsMapNonEmptyBody, curTime: mockTime, }, want: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlZHRzIjoiY2hoa0NWRUt" + "ET2FXNi1zXzNHVnBkZnk4UzJ3czBLRlhzRkprdUZzTnhWNCIsInYiOiIxIiwiZ" + "XhwIjoxNjU2MzIyMTQ4LCJlaHRzIjoiQXV0aG9yaXphdGlvbjt1cmk7aHR0cC1" + "tZXRob2Q7Ym9keTtDb250ZW50LVR5cGUiLCJpYXQiOjE2NTYzMjIwMjgsImp0a" + "SI6ImIwNGIwMzhlLTUyZjAtYjdkMC05NWY5LTFjYjA0NDc1ZjJhYiJ9.nz7viG" + "O2cqyoGAarHALoIy0FbX2mlG6esweuJk8ZRvw0xmoH7oR1wHdwnkgRB2gar_Fe" + "I42Ni2AjWzOYY26siEiJDM0Nv7qbiCC6SZpCq3xYYwN27Ky41m74eqh8wYod-T" + "5sN-vLqVDLIewFLQ7EftQ-d8a2VLKO4NyL9F0yHjXOn5LEsAzNRBNDEOYebIHD" + "mF4wFRVTm5MJMyYlKj04kDojRb5111FNe68POblY5n1SZyrbSnAE4qLrPrz65I" + "lpRnkqln9ORGx62EG8UpAP8RQf_oKZ1ZGbmI3KB1t0lhMW59lUT2mYCJZ9sRaQ" + "RO3VKERfNYZtOq6xprhKVl55mQ", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := g.generate(tt.args.ehts, tt.args.curTime) if (err != nil) != tt.wantErr { t.Errorf("generate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("generate() got = %v, want %v", got, tt.want) } }) } } func Test_buildClientSecret(t *testing.T) { want := mockClientSecret if got := buildClientSecret("test", "test"); got != want { t.Errorf("buildClientSecret() = %v, want %v", got, want) } } func Test_sign(t *testing.T) { type args struct { claims []byte signer jose.Signer } tests := []struct { name string args args want string wantErr bool }{ { name: "sign-success", args: args{[]byte("claims"), mockSigner}, want: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.Y2xhaW1z.UpFWT1xyFSNFOi" + "vlI83g1187to8V0Mw6Bfz6NIwGJE-n_turYNpLDCEjoAJmAFhYQHb289JLIoLC" + "WOET4dbh0Od2mNZODNIZhY_Xu7hlVLu7bPX1Fvl7rC4UiJYVKoZKyc7924pvJP" + "ndmKnIwrt_hygkO3GEBCpkxI57_7lNyBXtYVqSGQyayV0Vq55673uC4egdnjNv" + "utC6JGsSnY1PekQbT4YyVgqZeTCOI0sKNtEKzNVtgr6qXs7VOYxzAOAH9kjOSK" + "TtZ66VSCbq_TF9F08fEx9X_sCGW58K4HT_EeuXUW4EDUgfZFqoPtmzej50wQX9" + "IiaaAtJ5CVxMcdf9Pw", wantErr: false, }, { name: "sign-fail", args: args{[]byte("claims"), nil}, want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := sign(tt.args.signer, tt.args.claims) if (err != nil) != tt.wantErr { t.Errorf("sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("sign() got = %v, want %v", got, tt.want) } }) } }