Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions pkg/connectors/microcks_client_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package connectors

import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/microcks/microcks-cli/pkg/config"
)

func TestUploadArtifactStreamsWithoutBuffering(t *testing.T) {
Expand Down Expand Up @@ -102,3 +109,163 @@ func TestDownloadArtifactReturnsResponseBody(t *testing.T) {
t.Fatalf("expected response body %q, got %q", expectedBody, msg)
}
}

func createDummyJWT(exp int64) string {
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`))
payload := base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf(`{"exp":%d}`, exp)))
return header + "." + payload + "."
}

func TestRefreshAuthToken_ValidTokenNoRefresh(t *testing.T) {
// A token with expiration 1 hour in the future
futureTime := time.Now().Add(1 * time.Hour).Unix()
dummyToken := createDummyJWT(futureTime)

// Setup local config
localCfg := &config.LocalConfig{
CurrentContext: "test-context",
Contexts: []config.ContextRef{
{Name: "test-context", Server: "localhost", User: "test-user"},
},
Servers: []config.Server{
{Name: "localhost", Server: "localhost"},
},
Users: []config.User{
{Name: "test-user", AuthToken: dummyToken, RefreshToken: "some-refresh-token"},
},
}

mc := &microcksClient{
AuthToken: dummyToken,
RefreshToken: "some-refresh-token",
}

// Calling refreshAuthToken with a valid token should do nothing and return nil
err := mc.refreshAuthToken(localCfg, "test-context", "")
if err != nil {
t.Fatalf("refreshAuthToken failed: %v", err)
}

// Verify token was not modified
if mc.AuthToken != dummyToken {
t.Errorf("expected AuthToken to remain %q, got %q", dummyToken, mc.AuthToken)
}
}

func TestRefreshAuthToken_ExpiredTokenTriggersRefresh(t *testing.T) {
// A token with expiration 1 hour in the past
pastTime := time.Now().Add(-1 * time.Hour).Unix()
expiredToken := createDummyJWT(pastTime)

// We need a temporary config file path since the function calls WriteLocalConfig
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "config.yaml")

// Setup local config. Note: refreshAuthToken uses the context name ("test-context")
// as the name of the user to upsert, so we name the user "test-context" to match.
localCfg := &config.LocalConfig{
CurrentContext: "test-context",
Contexts: []config.ContextRef{
{Name: "test-context", Server: "http://localhost", User: "test-context"},
},
Servers: []config.Server{
{Server: "http://localhost"},
},
Users: []config.User{
{Name: "test-context", AuthToken: expiredToken, RefreshToken: "old-refresh-token"},
},
Auths: []config.Auth{
{Server: "http://localhost", ClientId: "cli", ClientSecret: "secret"},
},
}

// Write initial localconfig to the temp file
if err := config.WriteLocalConfig(*localCfg, configPath); err != nil {
t.Fatalf("failed to write local config: %v", err)
}

// Spin up mock server handling Microcks client / Keycloak routes
var mockServer *httptest.Server
mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/api/keycloak/config":
// Return keycloak config pointing to this mock server
resp := map[string]interface{}{
"enabled": true,
"auth-server-url": mockServer.URL,
"realm": "microcks",
}
json.NewEncoder(w).Encode(resp)
case "/realms/microcks/.well-known/openid-configuration":
// Return OIDC metadata pointing to token endpoint on mock server
resp := map[string]string{
"authorization_endpoint": mockServer.URL + "/realms/microcks/protocol/openid-connect/auth",
"token_endpoint": mockServer.URL + "/realms/microcks/protocol/openid-connect/token",
}
json.NewEncoder(w).Encode(resp)
case "/realms/microcks/protocol/openid-connect/token":
// Verify request body for refresh token grant
if err := r.ParseForm(); err != nil {
t.Fatalf("failed to parse form: %v", err)
}
if r.FormValue("grant_type") != "refresh_token" {
t.Errorf("unexpected grant_type: %q", r.FormValue("grant_type"))
}
if r.FormValue("refresh_token") != "old-refresh-token" {
t.Errorf("unexpected refresh_token: %q", r.FormValue("refresh_token"))
}

// Return new tokens
resp := map[string]string{
"access_token": "new-access-token",
"refresh_token": "new-refresh-token",
}
json.NewEncoder(w).Encode(resp)
default:
t.Fatalf("unexpected request to: %s", r.URL.Path)
}
}))
defer mockServer.Close()

apiURL, err := url.Parse(mockServer.URL + "/api/")
if err != nil {
t.Fatalf("failed to parse URL: %v", err)
}

mc := &microcksClient{
APIURL: apiURL,
AuthToken: expiredToken,
RefreshToken: "old-refresh-token",
httpClient: mockServer.Client(),
}

err = mc.refreshAuthToken(localCfg, "test-context", configPath)
if err != nil {
t.Fatalf("refreshAuthToken failed: %v", err)
}

// Verify client tokens were updated
if mc.AuthToken != "new-access-token" {
t.Errorf("expected AuthToken to be refreshed to %q, got %q", "new-access-token", mc.AuthToken)
}
if mc.RefreshToken != "new-refresh-token" {
t.Errorf("expected RefreshToken to be refreshed to %q, got %q", "new-refresh-token", mc.RefreshToken)
}

// Verify local config was updated and written back to file
updatedCfg, err := config.ReadLocalConfig(configPath)
if err != nil {
t.Fatalf("failed to read back config: %v", err)
}
user, err := updatedCfg.GetUser("test-context")
if err != nil {
t.Fatalf("failed to get user: %v", err)
}
if user.AuthToken != "new-access-token" {
t.Errorf("expected config AuthToken to be %q, got %q", "new-access-token", user.AuthToken)
}
if user.RefreshToken != "new-refresh-token" {
t.Errorf("expected config RefreshToken to be %q, got %q", "new-refresh-token", user.RefreshToken)
}
}
Loading