Scert/backend/internal/nginxactivation/request_linux_test.go

165 lines
5.2 KiB
Go

package nginxactivation
import (
"context"
"errors"
"os"
"path/filepath"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/processlock"
)
func requestIDForTest(t *testing.T) string {
t.Helper()
id, err := newID()
if err != nil {
t.Fatal(err)
}
return id
}
func TestRequestFencePersistsAcrossRestartAndNextTransaction(t *testing.T) {
f := newRotationFixture(t)
id := requestIDForTest(t)
for range 2 {
state, err := f.rotation.ResolveRequest(t.Context(), id)
if err != nil || state.ID != id || state.Status != "canceled" {
t.Fatal("未送达请求应持久化取消栅栏", state, err)
}
}
nextID := requestIDForTest(t)
if _, err := f.rotation.StageRequest(t.Context(), nextID, f.candidate.chain, f.candidate.key); err != nil {
t.Fatal(err)
}
f.upstream.active.Store(&f.candidate.pair)
if err := f.rotation.Commit(t.Context(), nextID); err != nil {
t.Fatal(err)
}
restarted := f.rotation
if _, err := restarted.StageRequest(t.Context(), id, f.candidate.chain, f.candidate.key); err == nil {
t.Fatal("重启且后续事务提交后仍须拒绝旧请求")
}
if _, err := restarted.StageRequest(t.Context(), nextID, f.candidate.chain, f.candidate.key); err == nil {
t.Fatal("已经提交的请求不能重放")
}
if f.reloader.reloadCount != 1 {
t.Fatal("重复请求触发了额外重载")
}
}
func TestRequestReservationFailureDoesNotActivate(t *testing.T) {
for _, invalid := range []string{"symlink", "corrupt", "directory"} {
t.Run(invalid, func(t *testing.T) {
f := newRotationFixture(t)
id := requestIDForTest(t)
path := filepath.Join(f.rotation.Directory, "request-"+id+".json")
switch invalid {
case "symlink":
if err := os.Symlink("missing", path); err != nil {
t.Fatal(err)
}
case "directory":
if err := os.Mkdir(path, 0o700); err != nil {
t.Fatal(err)
}
case "corrupt":
writeTestFile(t, path, []byte(`{"status":"canceled"}`))
}
if _, err := f.rotation.StageRequest(t.Context(), id, f.candidate.chain, f.candidate.key); err == nil {
t.Fatal("无法可信保存请求时不能激活")
}
if _, err := f.rotation.ResolveRequest(t.Context(), id); err == nil {
t.Fatal("不能把损坏请求记录当成尚未执行")
}
if f.reloader.reloadCount != 0 {
t.Fatal("请求记录故障不能重载代理")
}
})
}
}
func TestRequestResolveCannotOvertakeActiveStage(t *testing.T) {
f := newRotationFixture(t)
id := requestIDForTest(t)
started, release := make(chan struct{}), make(chan struct{})
f.reloader.check = func(number int) error {
if number == 1 {
close(started)
<-release
}
return nil
}
done := make(chan error, 1)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
go func() {
_, err := f.rotation.StageRequest(ctx, id, f.candidate.chain, f.candidate.key)
done <- err
}()
select {
case <-started:
case <-time.After(3 * time.Second):
close(release)
t.Fatal("暂存没有开始")
}
_, err := f.rotation.ResolveRequest(ctx, id)
close(release)
stageErr := <-done
if !errors.Is(err, processlock.ErrLocked) || stageErr != nil {
t.Fatal("恢复不能越过正在执行的暂存", err, stageErr)
}
state, err := f.rotation.ResolveRequest(ctx, id)
if err != nil || state.ID != id || state.Status != "staged" {
t.Fatal("已执行请求应返回对应事务", state, err)
}
}
func TestRequestReservationBeforeTransactionCanBeCanceled(t *testing.T) {
f := newRotationFixture(t)
id := requestIDForTest(t)
if _, err := f.rotation.StageRequest(t.Context(), id, []byte("invalid"), []byte("invalid")); err == nil {
t.Fatal("无效材料必须拒绝")
}
state, err := f.rotation.ResolveRequest(t.Context(), id)
if err != nil || state.Status != "canceled" {
t.Fatal("已占位但未写入事务的请求应允许取消", state, err)
}
if _, err := f.rotation.StageRequest(t.Context(), id, f.candidate.chain, f.candidate.key); err == nil {
t.Fatal("同一编号不能在失败后替换材料重试")
}
}
func TestRequestResolveRefusesChangedBaseline(t *testing.T) {
f := newRotationFixture(t)
id := requestIDForTest(t)
if _, err := f.rotation.StageRequest(t.Context(), id, []byte("invalid"), []byte("invalid")); err == nil {
t.Fatal("无效材料必须拒绝")
}
if _, err := f.rotation.Stage(t.Context(), f.candidate.chain, f.candidate.key); err != nil {
t.Fatal(err)
}
if _, err := f.rotation.ResolveRequest(t.Context(), id); err == nil {
t.Fatal("基准事务已变化时不能推断旧占位尚未执行")
}
f.assertStatus(t, "staged")
if f.reloader.reloadCount != 1 {
t.Fatal("恢复查询不能改动无关事务")
}
}
func TestRequestProtocolRejectsMissingIDAndExtraMaterial(t *testing.T) {
id := requestIDForTest(t)
for _, message := range []request{
{Version: protocolVersion, Action: "stage_request", Chain: []byte("chain"), Key: []byte("key")},
{Version: protocolVersion, Action: "stage_request", ID: "../request", Chain: []byte("chain"), Key: []byte("key")},
{Version: protocolVersion, Action: "resolve_request"},
{Version: protocolVersion, Action: "resolve_request", ID: id, Chain: []byte("chain")},
} {
if validRequest(message) {
t.Fatal("无效请求不应通过协议检查", message.Action)
}
}
}