從 edge-ai-platform POC 轉為正式產品的雲端後端,含以下整合階段:
- Phase 0:雛形骨架 — `cmd/api-server` (REST :3721) + `cmd/remote-proxy`
(tunnel :3800 / internal :3801) 雙 binary 共用 internal/,沿用 POC 的
WebSocket+yamux tunnel 協定但解耦 relay 與 API
- Phase 0.6:OIDC BFF 接 Innovedus Member Center
- internal/oidc package(coreos/go-oidc + PKCE S256 + state + nonce)
- internal/usersession package(HMAC-SHA256 cookie + RotateSessionID
防 session fixation, OWASP ASVS V3.2.1)
- 4 個 OIDC handler(/api/auth/login|callback|me|logout)+ AuthMiddleware
- 完全拔除 StaticAuthProvider,OIDC 是唯一認證路徑
- 9 個 ADR(含 ADR-010 BFF / ADR-011 取代 static auth /
ADR-012 pending session shared cookie / ADR-013 PKCE-only public client)
- Phase 0.7:A1 改造 + security audit 修復
- OIDC ClientSecret 變選填,支援 stage MC 的 public PKCE-only client
(AuthStyleInParams 強制 token endpoint 不送 client_secret)
- 預留 ServiceClient* 欄位給未來 client_credentials grant
- 移除 13+ 處 resolveUserID(uc, StaticUserID) fallback 改 strict mode
(Audit C1:multi-tenant 隔離破口)
- Pairing exchange MarkUsed 失敗 abort + revoke session token(Audit M3)
- 新增 all_endpoints_require_auth_test 整合測試(51 endpoint × 401)
驗證:go test -race -count=3 ./... 17 packages 全綠 / go vet 0 warning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
2.7 KiB
Go
105 lines
2.7 KiB
Go
package wsconn
|
||
|
||
import (
|
||
"io"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/gorilla/websocket"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// 基本煙測:確認 wsconn 能在真實 WebSocket 之上做雙向傳輸。
|
||
//
|
||
// 不深度測試(邏輯與 POC 完全一致,POC 已驗證);此測試主要保證:
|
||
// - package 可編譯
|
||
// - New / Read / Write / Close / SetReadDeadline 可實際運作
|
||
// - 符合 net.Conn 介面
|
||
func TestConn_ReadWrite(t *testing.T) {
|
||
// 啟動一個 echo server:把收到的 bytes 原封寫回。
|
||
var upgrader = websocket.Upgrader{
|
||
CheckOrigin: func(r *http.Request) bool { return true },
|
||
}
|
||
|
||
done := make(chan struct{})
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
ws, err := upgrader.Upgrade(w, r, nil)
|
||
if err != nil {
|
||
t.Errorf("upgrade failed: %v", err)
|
||
return
|
||
}
|
||
defer ws.Close()
|
||
defer close(done)
|
||
|
||
conn := New(ws)
|
||
// 讀一整塊 → 寫回
|
||
buf := make([]byte, 64)
|
||
n, err := conn.Read(buf)
|
||
if err != nil {
|
||
t.Errorf("server read failed: %v", err)
|
||
return
|
||
}
|
||
if _, err := conn.Write(buf[:n]); err != nil {
|
||
t.Errorf("server write failed: %v", err)
|
||
}
|
||
}))
|
||
defer server.Close()
|
||
|
||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/"
|
||
|
||
dialer := websocket.DefaultDialer
|
||
rawClient, _, err := dialer.Dial(wsURL, nil)
|
||
require.NoError(t, err)
|
||
defer rawClient.Close()
|
||
|
||
client := New(rawClient)
|
||
|
||
// 寫 → 讀回
|
||
payload := []byte("hello wsconn")
|
||
_, err = client.Write(payload)
|
||
require.NoError(t, err)
|
||
|
||
buf := make([]byte, len(payload))
|
||
_, err = io.ReadFull(client, buf)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, payload, buf)
|
||
|
||
// 等 server goroutine 結束
|
||
select {
|
||
case <-done:
|
||
case <-time.After(2 * time.Second):
|
||
t.Fatal("server goroutine 未在 2s 內完成")
|
||
}
|
||
}
|
||
|
||
func TestConn_SetDeadline_ExpiredReadReturnsError(t *testing.T) {
|
||
// 建一個永遠不送資料的 server,讓 client read deadline 觸發 timeout。
|
||
var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
ws, err := upgrader.Upgrade(w, r, nil)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer ws.Close()
|
||
// 故意 block — 等 client 斷開
|
||
_, _, _ = ws.NextReader()
|
||
}))
|
||
defer server.Close()
|
||
|
||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/"
|
||
raw, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||
require.NoError(t, err)
|
||
defer raw.Close()
|
||
|
||
c := New(raw)
|
||
require.NoError(t, c.SetReadDeadline(time.Now().Add(50*time.Millisecond)))
|
||
|
||
buf := make([]byte, 16)
|
||
_, err = c.Read(buf)
|
||
assert.Error(t, err, "過期 deadline 下 Read 應回 error")
|
||
}
|