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
25 changes: 13 additions & 12 deletions gen/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions runs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,21 @@ docker-postgres-stop: ## Stop and remove PostgreSQL container
@docker stop flyte-runs-postgres || true
@docker rm flyte-runs-postgres || true

##@ Testing

# Override test to exclude API tests
.PHONY: test
test: ## Run unit tests only (excludes test/)
@echo "Running unit tests..."
@go test -v -race -cover $(shell go list ./... | grep -v '/test')

##@ Integration Tests

.PHONY: api-test
api-test: ## Run API integration tests
@echo "Running API integration tests..."
@go test -v -race ./test/api/...

.PHONY: integration-test
integration-test: build-fast build-client ## Run integration test with SQLite
@echo "Running service with SQLite..."
Expand Down
9 changes: 9 additions & 0 deletions runs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ go test ./...
make test
```

### Run API tests

Pleaes ensure the service is started by following [Quick Start with SQLite](#quick-start-with-sqlite) section

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small typo here -> "Please"

before running API tests.

```sh
make api-test
```

### Run integration test with client

**Using SQLite:**
Expand Down
158 changes: 158 additions & 0 deletions runs/test/api/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package api

import (
"context"
"fmt"
"log"
"net/http"
"os"
"testing"
"time"

"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"gorm.io/gorm"

"github.com/flyteorg/flyte/v2/flytestdlib/database"
"github.com/flyteorg/flyte/v2/flytestdlib/logger"
"github.com/flyteorg/flyte/v2/gen/go/flyteidl2/task/taskconnect"
"github.com/flyteorg/flyte/v2/runs/migrations"
"github.com/flyteorg/flyte/v2/runs/repository"
"github.com/flyteorg/flyte/v2/runs/service"
)

const (
testPort = 8091 // Different port to avoid conflicts with main service

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just pure out of curiosity. Will people have their port 8091 being used❓

testDBFile = "/tmp/flyte-runs-test.db" // Temporary database file for tests
)

var (
endpoint string
testServer *http.Server
testDB *gorm.DB // Expose DB for cleanup
)


// TestMain sets up the test environment with SQLite database and runs service
func TestMain(m *testing.M) {
ctx := context.Background()
var exitCode int
defer func() {
os.Exit(exitCode)
}()

// Setup: Create SQLite database
// NOTE: Using a temp file instead of :memory: to avoid GORM AutoMigrate issues
// with index creation on fresh in-memory databases
dbConfig := &database.DbConfig{
SQLite: database.SQLiteConfig{
File: testDBFile,
},
}
logCfg := logger.GetConfig()
var err error
testDB, err = database.GetDB(ctx, dbConfig, logCfg)
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
log.Println("Database initialized")

// Run migrations
if err := migrations.RunMigrations(testDB); err != nil {
log.Fatalf("Failed to run migrations: %v", err)
}
log.Println("Database migrations completed")

// Create repository and services
repo := repository.NewRepository(testDB)
taskSvc := service.NewTaskService(repo)

// Setup HTTP server
mux := http.NewServeMux()
taskPath, taskHandler := taskconnect.NewTaskServiceHandler(taskSvc)
mux.Handle(taskPath, taskHandler)

// Add health check
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})

endpoint = fmt.Sprintf("http://localhost:%d", testPort)
testServer = &http.Server{
Addr: fmt.Sprintf(":%d", testPort),
Handler: h2c.NewHandler(mux, &http2.Server{}),
}

// Start server in background
go func() {
log.Printf("Test server starting on %s", endpoint)
if err := testServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("Server error: %v", err)
}
}()

// Wait for server to be ready
if !waitForServer(endpoint, 10*time.Second) {
log.Fatal("Test server failed to start")
}
Comment on lines +88 to +98

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the server fails to bind to port 8091, besides logging, the code still keeps on running for 10 seconds. Do you think it's a good idea to stop early whenever the server fails to bind in the goroutine?

log.Println("Test server is ready")

// Run tests
exitCode = m.Run()

// Teardown: Stop server
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := testServer.Shutdown(shutdownCtx); err != nil {
log.Printf("Test server shutdown error: %v", err)
}
log.Println("Test server stopped")

// Cleanup: Remove test database file
if err := os.Remove(testDBFile); err != nil && !os.IsNotExist(err) {
log.Printf("Warning: Failed to remove test database: %v", err)
}
Comment on lines +104 to +115

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here cleanup only runs if we reach the end of TestMain. If there’s a panic or early exit during setup, the server and DB file might be left around. Would it make sense to move the cleanup into the deferred block so it always runs?

}

// waitForServer waits for the server to be ready
func waitForServer(url string, timeout time.Duration) bool {
client := &http.Client{Timeout: 1 * time.Second}
deadline := time.Now().Add(timeout)

for time.Now().Before(deadline) {
resp, err := client.Get(url + "/healthz")
if err == nil && resp.StatusCode == http.StatusOK {
resp.Body.Close()
return true
}
if resp != nil {
resp.Body.Close()
}
time.Sleep(100 * time.Millisecond)
}
return false
}

// cleanupTestDB clears all tables in the test database
// This ensures each test starts with a clean state
func cleanupTestDB(t *testing.T) {
t.Helper()

if testDB == nil {
t.Log("Warning: testDB is nil, skipping cleanup")
return
}

// Delete all records from all tables
// Order matters due to foreign key constraints (if any)
tables := []string{"tasks", "task_specs", "actions"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I acknowledge we only got these three tables right now. But what do you feel about sending query for dynamic table check?


for _, table := range tables {
if err := testDB.Exec(fmt.Sprintf("DELETE FROM %s", table)).Error; err != nil {
t.Logf("Warning: Failed to cleanup table %s: %v", table, err)
}
}
Comment on lines +151 to +155

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'll be better to change it to t.Fatalf() to stop the function as soon as the deletion of database was errored, so we wouldn't have used the broken database down the way. What do you think about this?


t.Log("Test database cleaned up")
}
75 changes: 75 additions & 0 deletions runs/test/api/task_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package api

import (
"context"
"testing"

"connectrpc.com/connect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/flyteorg/flyte/v2/gen/go/flyteidl2/core"
"github.com/flyteorg/flyte/v2/gen/go/flyteidl2/task"
"github.com/flyteorg/flyte/v2/gen/go/flyteidl2/task/taskconnect"
)

const (
testOrg = "test-org"
testProject = "test-project"
testDomain = "test-domain"
)

func TestDeployTask(t *testing.T) {
t.Cleanup(func() {
cleanupTestDB(t)
})

ctx := context.Background()
httpClient := newClient()
opts := []connect.ClientOption{}

taskClient := taskconnect.NewTaskServiceClient(httpClient, endpoint, opts...)

// Deploy a task
taskID := &task.TaskIdentifier{
Org: testOrg,
Project: testProject,
Domain: testDomain,
Name: "test-task",
Version: uniqueString(),
}

deployResp, err := taskClient.DeployTask(ctx, connect.NewRequest(&task.DeployTaskRequest{
TaskId: taskID,
Spec: &task.TaskSpec{
TaskTemplate: &core.TaskTemplate{
Type: "container",
Target: &core.TaskTemplate_Container{
Container: &core.Container{
Image: "alpine:latest",
Args: []string{"echo", "hello"},
},
},
},
},
}))

require.NoError(t, err)
require.NotNil(t, deployResp)
t.Logf("Task deployed successfully: %v", taskID)

// Get task details
getTaskDetailsResp, err := taskClient.GetTaskDetails(ctx, connect.NewRequest(&task.GetTaskDetailsRequest{
TaskId: taskID,
}))

require.NoError(t, err)
require.NotNil(t, getTaskDetailsResp)
details := getTaskDetailsResp.Msg.GetDetails()
assert.Equal(t, taskID.GetOrg(), details.GetTaskId().GetOrg())
assert.Equal(t, taskID.GetProject(), details.GetTaskId().GetProject())
assert.Equal(t, taskID.GetDomain(), details.GetTaskId().GetDomain())
assert.Equal(t, taskID.GetName(), details.GetTaskId().GetName())
assert.Equal(t, taskID.GetVersion(), details.GetTaskId().GetVersion())
t.Logf("Task details retrieved successfully: %v", details)
}
25 changes: 25 additions & 0 deletions runs/test/api/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package api

import (
"fmt"
"net/http"
"os"
"time"
)

func getEnvOrDefault(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultValue
}
Comment on lines +10 to +15

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think this function was never used right 🤔


func uniqueString() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}

func newClient() *http.Client {
return &http.Client{
Timeout: 30 * time.Second,
}
}
File renamed without changes.
File renamed without changes.