mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
183 lines
5.2 KiB
Go
183 lines
5.2 KiB
Go
package control
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
)
|
|
|
|
type frameFixtures struct {
|
|
RPCVersion int `json:"rpc_version"`
|
|
ValidRequests []struct {
|
|
Name string `json:"name"`
|
|
Method string `json:"method"`
|
|
Line string `json:"line"`
|
|
} `json:"valid_requests"`
|
|
InvalidRequests []struct {
|
|
Name string `json:"name"`
|
|
ExpectCode string `json:"expect_code"`
|
|
Line string `json:"line"`
|
|
} `json:"invalid_requests"`
|
|
UndeclaredMethods []struct {
|
|
Name string `json:"name"`
|
|
ExpectCode string `json:"expect_code"`
|
|
Line string `json:"line"`
|
|
} `json:"undeclared_methods"`
|
|
Responses []struct {
|
|
Name string `json:"name"`
|
|
OK bool `json:"ok"`
|
|
Line string `json:"line"`
|
|
} `json:"responses"`
|
|
}
|
|
|
|
func loadFixtures(t *testing.T) frameFixtures {
|
|
t.Helper()
|
|
data, err := os.ReadFile("../../testdata/control/frames.json")
|
|
if err != nil {
|
|
t.Fatalf("read fixtures: %v", err)
|
|
}
|
|
var fixtures frameFixtures
|
|
if err := json.Unmarshal(data, &fixtures); err != nil {
|
|
t.Fatalf("parse fixtures: %v", err)
|
|
}
|
|
if fixtures.RPCVersion != Version {
|
|
t.Fatalf("fixtures are for protocol v%d, code is v%d",
|
|
fixtures.RPCVersion, Version)
|
|
}
|
|
return fixtures
|
|
}
|
|
|
|
// The fixtures are the shared reference for the Go side and the Lua bridge.
|
|
// If the decoder and the file ever disagree, one of them is lying to the other
|
|
// implementation about what the wire looks like.
|
|
func TestFixtureRequestsDecode(t *testing.T) {
|
|
fixtures := loadFixtures(t)
|
|
if len(fixtures.ValidRequests) == 0 {
|
|
t.Fatal("no valid request fixtures")
|
|
}
|
|
|
|
for _, fixture := range fixtures.ValidRequests {
|
|
t.Run(fixture.Name, func(t *testing.T) {
|
|
// Through the framing path, not just the decoder, so the fixture
|
|
// proves a real frame works end to end.
|
|
reader := bufio.NewReader(strings.NewReader(fixture.Line + "\n"))
|
|
request, err := ReadRequest(reader, MaxRequestBytes)
|
|
if err != nil {
|
|
t.Fatalf("rejected: %v", err)
|
|
}
|
|
if request.Method != fixture.Method {
|
|
t.Fatalf("method = %q, want %q", request.Method, fixture.Method)
|
|
}
|
|
if _, declared := Lookup(request.Method); !declared {
|
|
t.Fatalf("fixture uses %q, which is not in the catalogue", request.Method)
|
|
}
|
|
if strings.Contains(fixture.Line, "\n") {
|
|
t.Fatal("fixture line contains a raw newline; frames are one line")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixtureInvalidRequestsAreRejectedWithTheStatedCode(t *testing.T) {
|
|
fixtures := loadFixtures(t)
|
|
if len(fixtures.InvalidRequests) == 0 {
|
|
t.Fatal("no invalid request fixtures")
|
|
}
|
|
|
|
for _, fixture := range fixtures.InvalidRequests {
|
|
t.Run(fixture.Name, func(t *testing.T) {
|
|
_, err := DecodeRequest([]byte(fixture.Line))
|
|
if err == nil {
|
|
t.Fatal("accepted")
|
|
}
|
|
code, ok := domain.CodeOf(err)
|
|
if !ok || string(code) != fixture.ExpectCode {
|
|
t.Fatalf("code = %q, want %q (err: %v)", code, fixture.ExpectCode, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A well-formed frame naming a method that does not exist is NotFound, and
|
|
// nothing about the name is ever executed.
|
|
func TestFixtureUndeclaredMethodsAreNotFound(t *testing.T) {
|
|
fixtures := loadFixtures(t)
|
|
registry := NewRegistry()
|
|
|
|
for _, fixture := range fixtures.UndeclaredMethods {
|
|
t.Run(fixture.Name, func(t *testing.T) {
|
|
request, err := DecodeRequest([]byte(fixture.Line))
|
|
if err != nil {
|
|
t.Fatalf("the frame itself should be well formed: %v", err)
|
|
}
|
|
response := registry.Dispatch(context.Background(), request)
|
|
if response.OK {
|
|
t.Fatal("an undeclared method was served")
|
|
}
|
|
if response.Error.Code != fixture.ExpectCode {
|
|
t.Fatalf("code = %q, want %q", response.Error.Code, fixture.ExpectCode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixtureResponsesParseAndMatchTheEnvelope(t *testing.T) {
|
|
fixtures := loadFixtures(t)
|
|
if len(fixtures.Responses) == 0 {
|
|
t.Fatal("no response fixtures")
|
|
}
|
|
|
|
for _, fixture := range fixtures.Responses {
|
|
t.Run(fixture.Name, func(t *testing.T) {
|
|
var response Response
|
|
if err := json.Unmarshal([]byte(fixture.Line), &response); err != nil {
|
|
t.Fatalf("does not parse: %v", err)
|
|
}
|
|
if response.RPCVersion != Version {
|
|
t.Fatalf("rpc_version = %d", response.RPCVersion)
|
|
}
|
|
if response.OK != fixture.OK {
|
|
t.Fatalf("ok = %v, want %v", response.OK, fixture.OK)
|
|
}
|
|
if fixture.OK {
|
|
if response.Error != nil {
|
|
t.Fatal("a success carries an error")
|
|
}
|
|
return
|
|
}
|
|
if response.Error == nil {
|
|
t.Fatal("a failure carries no error")
|
|
}
|
|
if string(response.Result) != "null" {
|
|
t.Fatalf("result = %s, want null on a failure", response.Result)
|
|
}
|
|
// Retryability in the fixture must match what the code decides, or
|
|
// a client written against the file would retry the wrong things.
|
|
want := Retryable(domain.ErrorCode(response.Error.Code))
|
|
if response.Error.Retryable != want {
|
|
t.Fatalf("retryable = %v for %s, code says %v",
|
|
response.Error.Retryable, response.Error.Code, want)
|
|
}
|
|
for key := range response.Error.Details {
|
|
if !isAllowedDetailKey(key) {
|
|
t.Fatalf("detail key %q is not on the whitelist", key)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func isAllowedDetailKey(key string) bool {
|
|
for _, allowed := range AllowedDetailKeys() {
|
|
if allowed == key {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|