e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
329 lines
9.0 KiB
Go
329 lines
9.0 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package jupyter
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/alibaba/opensandbox/execd/pkg/jupyter/execute"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// Test integration flow: authentication -> get kernel specs -> create session -> execute code -> close session
|
|
func TestIntegrationFlow(t *testing.T) {
|
|
// Create mock HTTP server
|
|
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Handle authentication validation request
|
|
if r.URL.Path == "/api/status" {
|
|
// Check authentication token
|
|
auth := r.Header.Get("Authorization")
|
|
if auth != "token test-token" {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Return status information
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"status": "ok"}`))
|
|
return
|
|
}
|
|
|
|
// Handle kernel specs request
|
|
if r.URL.Path == "/api/kernelspecs" {
|
|
// Return kernel specs
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"default": "python3",
|
|
"kernelspecs": {
|
|
"python3": {
|
|
"name": "python3",
|
|
"display_name": "Python 3",
|
|
"language": "python"
|
|
}
|
|
}
|
|
}`))
|
|
return
|
|
}
|
|
|
|
// Handle session-related requests
|
|
if r.URL.Path == "/api/sessions" {
|
|
if r.Method == http.MethodGet {
|
|
// List sessions
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`[{
|
|
"id": "test-session-id",
|
|
"path": "/path/to/notebook.ipynb",
|
|
"name": "Test Session",
|
|
"type": "notebook",
|
|
"kernel": {
|
|
"id": "test-kernel-id",
|
|
"name": "python3"
|
|
}
|
|
}]`))
|
|
return
|
|
} else if r.Method == http.MethodPost {
|
|
// Create session
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{
|
|
"id": "test-session-id",
|
|
"path": "/path/to/notebook.ipynb",
|
|
"name": "Test Session",
|
|
"type": "notebook",
|
|
"kernel": {
|
|
"id": "test-kernel-id",
|
|
"name": "python3"
|
|
}
|
|
}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Handle specific session requests
|
|
if strings.HasPrefix(r.URL.Path, "/api/sessions/test-session-id") {
|
|
if r.Method == http.MethodDelete {
|
|
// Delete session
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
} else if r.Method == http.MethodPatch {
|
|
// Modify session
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"id": "test-session-id",
|
|
"path": "/path/to/updated-notebook.ipynb",
|
|
"name": "Updated Test Session",
|
|
"type": "notebook",
|
|
"kernel": {
|
|
"id": "test-kernel-id",
|
|
"name": "python3"
|
|
}
|
|
}`))
|
|
return
|
|
} else if r.Method == http.MethodGet {
|
|
// Get session
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"id": "test-session-id",
|
|
"path": "/path/to/notebook.ipynb",
|
|
"name": "Test Session",
|
|
"type": "notebook",
|
|
"kernel": {
|
|
"id": "test-kernel-id",
|
|
"name": "python3"
|
|
}
|
|
}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Handle kernel requests
|
|
if r.URL.Path == "/api/kernels" {
|
|
if r.Method == http.MethodGet {
|
|
// List kernels
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`[{
|
|
"id": "test-kernel-id",
|
|
"name": "python3",
|
|
"execution_state": "idle"
|
|
}]`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Handle specific kernel requests
|
|
if strings.HasPrefix(r.URL.Path, "/api/kernels/test-kernel-id") {
|
|
if r.Method == http.MethodGet {
|
|
// Get kernel
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"id": "test-kernel-id",
|
|
"name": "python3",
|
|
"execution_state": "idle"
|
|
}`))
|
|
return
|
|
} else if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/restart") {
|
|
// Restart kernel
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"id": "test-kernel-id",
|
|
"name": "python3",
|
|
"restarted": true
|
|
}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// If it's a WebSocket connection request, upgrade to WebSocket
|
|
if strings.HasSuffix(r.URL.Path, "/channels") {
|
|
// Return 404, as WebSocket connections will be handled by a dedicated WebSocket server
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// For other requests, return 404
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer httpServer.Close()
|
|
|
|
// Create mock WebSocket server for code execution
|
|
wsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !strings.HasSuffix(r.URL.Path, "/channels") {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Upgrade HTTP connection to WebSocket
|
|
upgrader := websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
}
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to upgrade connection to WebSocket: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
// Continuously handle WebSocket messages
|
|
for {
|
|
// Read request message
|
|
var msg execute.Message
|
|
err := conn.ReadJSON(&msg)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
// If it's an execute request, send mock response
|
|
if msg.Header.MessageType == string(execute.MsgExecuteRequest) {
|
|
// Send stream output
|
|
streamContent, _ := json.Marshal(execute.StreamOutput{
|
|
Name: execute.StreamStdout,
|
|
Text: "Hello from test WebSocket!\n",
|
|
})
|
|
|
|
streamMsg := execute.Message{
|
|
Header: execute.Header{
|
|
MessageID: "stream-msg-id",
|
|
Session: msg.Header.Session,
|
|
MessageType: string(execute.MsgStream),
|
|
},
|
|
ParentHeader: msg.Header,
|
|
Content: json.RawMessage(streamContent),
|
|
}
|
|
conn.WriteJSON(streamMsg)
|
|
|
|
// Send execution result
|
|
resultContent, _ := json.Marshal(execute.ExecuteResult{
|
|
ExecutionCount: 1,
|
|
Data: map[string]interface{}{
|
|
"text/plain": "Integration test result",
|
|
},
|
|
Metadata: map[string]interface{}{},
|
|
})
|
|
|
|
executeResultMsg := execute.Message{
|
|
Header: execute.Header{
|
|
MessageID: "result-msg-id",
|
|
Session: msg.Header.Session,
|
|
MessageType: string(execute.MsgExecuteResult),
|
|
},
|
|
ParentHeader: msg.Header,
|
|
Content: json.RawMessage(resultContent),
|
|
}
|
|
conn.WriteJSON(executeResultMsg)
|
|
|
|
// Send status message
|
|
statusContent, _ := json.Marshal(execute.StatusUpdate{
|
|
ExecutionState: execute.StateIdle,
|
|
})
|
|
|
|
statusMsg := execute.Message{
|
|
Header: execute.Header{
|
|
MessageID: "status-msg-id",
|
|
Session: msg.Header.Session,
|
|
MessageType: string(execute.MsgStatus),
|
|
},
|
|
ParentHeader: msg.Header,
|
|
Content: json.RawMessage(statusContent),
|
|
}
|
|
conn.WriteJSON(statusMsg)
|
|
}
|
|
}
|
|
}))
|
|
defer wsServer.Close()
|
|
|
|
// Create Jupyter client
|
|
client := NewClient(httpServer.URL)
|
|
client.SetToken("test-token")
|
|
|
|
// Test 1: Validate authentication
|
|
status, err := client.ValidateAuth()
|
|
if err != nil {
|
|
t.Fatalf("Authentication validation failed: %v", err)
|
|
}
|
|
if status != "ok" {
|
|
t.Errorf("Authentication status incorrect, expected 'ok', got '%s'", status)
|
|
}
|
|
|
|
// Test 2: Get kernel specs
|
|
specs, err := client.GetKernelSpecs()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get kernel specs: %v", err)
|
|
}
|
|
if specs.Default != "python3" {
|
|
t.Errorf("Default kernel incorrect, expected 'python3', got '%s'", specs.Default)
|
|
}
|
|
if len(specs.Kernelspecs) != 1 {
|
|
t.Errorf("Kernel count incorrect, expected 1, got %d", len(specs.Kernelspecs))
|
|
}
|
|
|
|
// Test 3: Create session
|
|
session, err := client.CreateSession("Test Session", "/path/to/notebook.ipynb", "python3")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create session: %v", err)
|
|
}
|
|
if session.ID != "test-session-id" {
|
|
t.Errorf("Session ID incorrect, expected 'test-session-id', got '%s'", session.ID)
|
|
}
|
|
if session.Kernel.ID != "test-kernel-id" {
|
|
t.Errorf("Kernel ID incorrect, expected 'test-kernel-id', got '%s'", session.Kernel.ID)
|
|
}
|
|
|
|
// Modify WebSocket URL to point to WebSocket test server
|
|
wsURL := "ws" + strings.TrimPrefix(wsServer.URL, "http") + "/api/kernels/test-kernel-id/channels"
|
|
|
|
// Test 4: Connect to kernel and execute code
|
|
executor := execute.NewExecutor(wsURL, nil)
|
|
err = executor.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Failed to connect to kernel: %v", err)
|
|
}
|
|
defer executor.Disconnect()
|
|
|
|
// Execute code
|
|
err = executor.ExecuteCodeWithCallback("print('Hello from integration test!')", execute.CallbackHandler{})
|
|
if err != nil {
|
|
t.Fatalf("Failed to execute code: %v", err)
|
|
}
|
|
|
|
// Test 5: Delete session
|
|
err = client.DeleteSession(session.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete session: %v", err)
|
|
}
|
|
}
|