Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 0 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ jobs:
CAPY_PROJECT_ID: ${{ secrets.CAPY_PROJECT_ID }}
CAPY_SERVER_REGION: ${{ secrets.CAPY_SERVER_REGION }}
CAPY_THERAPY_SESSION_URL: ${{ secrets.CAPY_THERAPY_SESSION_URL }}
CAPY_AGENT_TOKEN: ${{ secrets.CAPY_AGENT_TOKEN }}
shell: bash

create-tag:
Expand Down
36 changes: 22 additions & 14 deletions internal/app/therapy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -12,15 +13,19 @@ import (
"time"

"github.com/google/uuid"
"google.golang.org/api/idtoken"
)

// Allow tests to inject a custom HTTP client builder.
var newTherapyHTTPClient = buildTherapyHTTPClient

// Internal configuration for therapy session calls
type therapyConfig struct {
baseURL string
token string
userID string
sessionID string
locale string
ctx context.Context
}

// Start therapy session
Expand All @@ -39,7 +44,11 @@ func callTherapySessionEndpoint(text string, session *Session) *string {
return nil
}

client := buildTherapyHTTPClient()
client, err := newTherapyHTTPClient(cfg.ctx, cfg.baseURL)
if err != nil {
log.Printf("[TherapySession] failed to create authenticated client: %v", err)
return nil
}

if !initTherapySession(client, cfg) {
return nil
Expand All @@ -58,9 +67,15 @@ func relayTherapyMessage(text string, session *Session) {
}
}

// Build an HTTP client configured for therapy session calls
func buildTherapyHTTPClient() *http.Client {
return &http.Client{Timeout: 120 * time.Second}
// Build an HTTP client configured with ID token authentication for therapy session calls
func buildTherapyHTTPClient(ctx context.Context, targetURL string) (*http.Client, error) {
client, err := idtoken.NewClient(ctx, targetURL)
if err != nil {
return nil, fmt.Errorf("failed to create ID token client: %w", err)
}
// Set timeout on the client
client.Timeout = 120 * time.Second
return client, nil
}

// Build configuration from environment and session; ensures a session ID exists
Expand All @@ -71,12 +86,6 @@ func buildTherapyConfig(session *Session) *therapyConfig {
return nil
}

token := os.Getenv("CAPY_AGENT_TOKEN")
if token == "" {
log.Printf("[TherapySession] missing CAPY_AGENT_TOKEN")
return nil
}

if session.User.TherapySessionId == nil || *session.User.TherapySessionId == "" {
newID := uuid.NewString()
session.User.TherapySessionId = &newID
Expand All @@ -88,10 +97,10 @@ func buildTherapyConfig(session *Session) *therapyConfig {

return &therapyConfig{
baseURL: baseURL,
token: token,
userID: userID,
sessionID: sessionID,
locale: locale,
ctx: context.Background(),
}
}

Expand All @@ -110,7 +119,7 @@ func initTherapySession(client *http.Client, cfg *therapyConfig) bool {
log.Printf("[TherapySession] init request build error: %v", err)
return false
}
req.Header.Set("Authorization", "Bearer "+cfg.token)
// ID token client automatically adds Authorization header
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
Expand Down Expand Up @@ -156,7 +165,6 @@ func sendTherapyMessage(client *http.Client, cfg *therapyConfig, text string) *s
log.Printf("[TherapySession] run request build error: %v", err)
return nil
}
req.Header.Set("Authorization", "Bearer "+cfg.token)
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
Expand Down
66 changes: 30 additions & 36 deletions internal/app/therapy_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package app

import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"context"
"net/http"
"net/http/httptest"
"testing"
"time"

"strings"
"strings"

"github.com/capymind/internal/database"
"github.com/capymind/internal/database"
)

func TestStartTherapySession(t *testing.T) {
Expand Down Expand Up @@ -56,11 +55,6 @@ func TestEndTherapySession(t *testing.T) {
func TestRelayTherapyMessage(t *testing.T) {
// Create a fake therapy session backend implementing both init and run endpoints
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer test-token" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
switch {
case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/apps/capymind_agent/users/u1/sessions/"):
// Session init endpoint
Expand All @@ -79,10 +73,14 @@ func TestRelayTherapyMessage(t *testing.T) {
}
}))
defer ts.Close()
os.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)
os.Setenv("CAPY_AGENT_TOKEN", "test-token")
defer os.Unsetenv("CAPY_THERAPY_SESSION_URL")
defer os.Unsetenv("CAPY_AGENT_TOKEN")
t.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)

// Inject simple HTTP client without Google auth for tests
originalBuilder := newTherapyHTTPClient
newTherapyHTTPClient = func(ctx context.Context, targetURL string) (*http.Client, error) {
return &http.Client{Timeout: 5 * time.Second}, nil
}
defer func() { newTherapyHTTPClient = originalBuilder }()

ctx := context.Background()
locale := "en"
Expand All @@ -91,7 +89,7 @@ func TestRelayTherapyMessage(t *testing.T) {

relayTherapyMessage("hi", session)

if len(session.Job.Output) == 0 {
if len(session.Job.Output) == 0 {
t.Fatalf("expected at least one output")
}
if session.Job.Output[0].TextID != "Hello, I'm here for you." {
Expand All @@ -116,11 +114,6 @@ func TestHandleSession_AutoEndWhenExpired(t *testing.T) {
func TestHandleSession_ForwardDuringActive(t *testing.T) {
// Fake backend implementing both init and run endpoints
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer test-token" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
switch {
case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/apps/capymind_agent/users/u1/sessions/"):
w.WriteHeader(http.StatusOK)
Expand All @@ -137,10 +130,13 @@ func TestHandleSession_ForwardDuringActive(t *testing.T) {
}
}))
defer ts.Close()
os.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)
os.Setenv("CAPY_AGENT_TOKEN", "test-token")
defer os.Unsetenv("CAPY_THERAPY_SESSION_URL")
defer os.Unsetenv("CAPY_AGENT_TOKEN")
t.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)

originalBuilder := newTherapyHTTPClient
newTherapyHTTPClient = func(ctx context.Context, targetURL string) (*http.Client, error) {
return &http.Client{Timeout: 5 * time.Second}, nil
}
defer func() { newTherapyHTTPClient = originalBuilder }()

ctx := context.Background()
future := time.Now().Add(5 * time.Minute)
Expand All @@ -160,11 +156,6 @@ func TestHandleSession_ForwardDuringActive(t *testing.T) {
func TestRelayTherapyMessage_ExistingSessionContinues(t *testing.T) {
// Fake backend: init returns 400 Session already exists; run_sse returns a reply
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer test-token" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
switch {
case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/apps/capymind_agent/users/u1/sessions/"):
w.WriteHeader(http.StatusBadRequest)
Expand All @@ -181,10 +172,13 @@ func TestRelayTherapyMessage_ExistingSessionContinues(t *testing.T) {
}
}))
defer ts.Close()
os.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)
os.Setenv("CAPY_AGENT_TOKEN", "test-token")
defer os.Unsetenv("CAPY_THERAPY_SESSION_URL")
defer os.Unsetenv("CAPY_AGENT_TOKEN")
t.Setenv("CAPY_THERAPY_SESSION_URL", ts.URL)

originalBuilder := newTherapyHTTPClient
newTherapyHTTPClient = func(ctx context.Context, targetURL string) (*http.Client, error) {
return &http.Client{Timeout: 5 * time.Second}, nil
}
defer func() { newTherapyHTTPClient = originalBuilder }()

ctx := context.Background()
locale := "en"
Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ source ./scripts/get_version.sh
APP_VERSION=$(get_version)

# Set environment variables
ENV_PARAMS=("CAPY_PROJECT_ID=$CAPY_PROJECT_ID" "CAPY_SERVER_REGION=$CAPY_SERVER_REGION" "CLOUD=true" "APP_VERSION=$APP_VERSION" "CAPY_THERAPY_SESSION_URL=$CAPY_THERAPY_SESSION_URL" "CAPY_AGENT_TOKEN=$CAPY_AGENT_TOKEN")
ENV_PARAMS=("CAPY_PROJECT_ID=$CAPY_PROJECT_ID" "CAPY_SERVER_REGION=$CAPY_SERVER_REGION" "CLOUD=true" "APP_VERSION=$APP_VERSION" "CAPY_THERAPY_SESSION_URL=$CAPY_THERAPY_SESSION_URL")
ENV_VARS=""
for PARAM in "${ENV_PARAMS[@]}"; do
ENV_VARS+="$PARAM,"
Expand Down