Scert/backend/internal/app/management_recovery_test.go

134 lines
3.5 KiB
Go

package app
import (
"context"
"errors"
"sync"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/httpapi"
)
func TestManagementRecoveryCloseCancelsAndWaits(t *testing.T) {
application := &App{}
started := make(chan struct{})
canceled := make(chan struct{})
release := make(chan struct{})
var once sync.Once
releaseRecovery := func() { once.Do(func() { close(release) }) }
t.Cleanup(releaseRecovery)
application.startManagementRecovery(func(ctx context.Context) error {
if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) > 100*time.Second {
return errors.New("恢复尝试必须有超时边界")
}
close(started)
<-ctx.Done()
close(canceled)
<-release
return ctx.Err()
})
t.Cleanup(func() {
releaseRecovery()
if err := application.Close(); err != nil {
t.Error(err)
}
})
select {
case <-started:
case <-time.After(3 * time.Second):
t.Fatal("恢复协程没有开始执行")
}
closed := make(chan error, 1)
go func() { closed <- application.Close() }()
select {
case <-canceled:
case <-time.After(3 * time.Second):
t.Fatal("关闭应用没有取消恢复尝试")
}
select {
case err := <-closed:
t.Fatal("恢复尝试退出前不能完成应用关闭", err)
default:
}
releaseRecovery()
select {
case err := <-closed:
if err != nil {
t.Fatal(err)
}
case <-time.After(3 * time.Second):
t.Fatal("恢复退出后应用关闭没有完成")
}
}
func TestManagementRecoveryCloseInterruptsRetryWait(t *testing.T) {
application := &App{}
returned := make(chan struct{})
application.startManagementRecovery(func(context.Context) error {
close(returned)
return errors.New("注入临时恢复失败")
})
t.Cleanup(func() { _ = application.Close() })
select {
case <-returned:
case <-time.After(3 * time.Second):
t.Fatal("首次恢复未执行")
}
closed := make(chan error, 1)
go func() { closed <- application.Close() }()
select {
case err := <-closed:
if err != nil {
t.Fatal(err)
}
case <-time.After(3 * time.Second):
t.Fatal("关闭应用不应等待整分钟的重试间隔")
}
}
func TestManagementRecoveryNilDoesNotStartWorker(t *testing.T) {
application := &App{}
application.startManagementRecovery(nil)
if application.managementStop != nil || application.managementDone != nil {
t.Fatal("未配置恢复逻辑时不应启动协程")
}
if err := application.Close(); err != nil {
t.Fatal(err)
}
}
func TestManagementRecoveryWaitsForHTTPS(t *testing.T) {
application := &App{HTTP: httpapi.New(httpapi.Dependencies{})}
called := make(chan struct{}, 1)
application.startManagementRecovery(func(context.Context) error {
called <- struct{}{}
return nil
})
if err := application.Close(); err != nil {
t.Fatal(err)
}
select {
case <-called:
t.Fatal("HTTPS 尚未监听时不能开始恢复")
default:
}
}
func TestManagementActivationRequiresHTTPSListener(t *testing.T) {
for _, server := range []*httpapi.Server{nil, httpapi.New(httpapi.Dependencies{})} {
if err := requireManagementListener(t.Context(), server); err == nil {
t.Fatal("没有监听器的桌面或嵌入式运行时不能申请管理证书")
}
activator := directManagementActivator{server: server}
if err := activator.Ready(t.Context(), httpapi.ManagementCertificateState{HTTPSMode: "direct"}); err == nil {
t.Fatal("直接模式不能绕过监听检查")
}
}
ctx, cancel := context.WithCancel(t.Context())
cancel()
if err := requireManagementListener(ctx, nil); !errors.Is(err, context.Canceled) {
t.Fatal("监听检查应保留取消原因", err)
}
}