197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
package scripts
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDobotAIDingTalkComposeOwnsApplicationAndPostgres(t *testing.T) {
|
|
compose, err := os.ReadFile("../deploy/dobotai-dingtalk/compose.yaml")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, required := range []string{
|
|
"dobotai-dingtalk:",
|
|
"container_name: dobotai-dingtalk",
|
|
"dobotai-postgres:",
|
|
"container_name: dobotai-postgres",
|
|
"dobotai-postgres-data:",
|
|
"depends_on:",
|
|
"condition: service_healthy",
|
|
"host.docker.internal:host-gateway",
|
|
"127.0.0.1:8090:8090",
|
|
"env_file:",
|
|
"read_only: true",
|
|
"/var/lib/dobotai/secrets",
|
|
} {
|
|
if !strings.Contains(string(compose), required) {
|
|
t.Errorf("compose missing %q", required)
|
|
}
|
|
}
|
|
|
|
configure, err := os.ReadFile("../deploy/dobotai-dingtalk/dobotai-dingtalk-configure")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, required := range []string{"configure_env.py", "dobotai-dingtalk-start"} {
|
|
if !strings.Contains(string(configure), required) {
|
|
t.Errorf("configure command missing %q", required)
|
|
}
|
|
}
|
|
|
|
bind, err := os.ReadFile("../deploy/dobotai-dingtalk/dobotai-dingtalk-bind-admin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, required := range []string{"docker exec -it dobotai-dingtalk", "/usr/local/bin/dobotai bootstrap-admin"} {
|
|
if !strings.Contains(string(bind), required) {
|
|
t.Errorf("bind command missing %q", required)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDobotAIDingTalkCredentialConfigurationNeverPassesSecretsOnCommandLine(t *testing.T) {
|
|
data, err := os.ReadFile("../deploy/dobotai-dingtalk/configure_env.py")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, required := range []string{"getpass.getpass", "os.replace", "0o600", "DINGTALK_CLIENT_SECRET"} {
|
|
if !strings.Contains(source, required) {
|
|
t.Errorf("credential configurator missing %q", required)
|
|
}
|
|
}
|
|
for _, forbidden := range []string{"subprocess.run([", "-v", "DINGTALK_CLIENT_SECRET="} {
|
|
if strings.Contains(source, forbidden) {
|
|
t.Errorf("credential configurator must not expose secret through process arguments: %q", forbidden)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDobotAIDingTalkCredentialConfigurationExplainsEveryInputWithRedactedExamples(t *testing.T) {
|
|
configurator, err := os.ReadFile("../deploy/dobotai-dingtalk/configure_env.py")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
documentation, err := os.ReadFile("../deploy/dobotai-dingtalk/README.md")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(configurator), "格式示例:{example}") || !strings.Contains(string(documentation), "格式示例") {
|
|
t.Fatal("configuration guidance must label every example")
|
|
}
|
|
for _, example := range []string{
|
|
"http://host.docker.internal:8080",
|
|
`{"dingtalk:*":"你的Agent ID"}`,
|
|
"DINGTALK_CLIENT_SECRET",
|
|
} {
|
|
if !strings.Contains(string(configurator), example) {
|
|
t.Errorf("configurator missing redacted example %q", example)
|
|
}
|
|
if !strings.Contains(string(documentation), example) {
|
|
t.Errorf("documentation missing redacted example %q", example)
|
|
}
|
|
}
|
|
if strings.Contains(string(configurator), "DATABASE_URL") {
|
|
t.Fatal("database URL must be deployment-managed, not requested interactively")
|
|
}
|
|
if !strings.Contains(string(documentation), "dobotai-postgres") {
|
|
t.Fatal("documentation must explain the Compose-managed database")
|
|
}
|
|
}
|
|
|
|
func TestDobotAIDingTalkStartRollsBackToPreviousContainerImage(t *testing.T) {
|
|
data, err := os.ReadFile("../deploy/dobotai-dingtalk/dobotai-dingtalk-start")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, required := range []string{
|
|
"previous_image=",
|
|
"set_runtime_image",
|
|
"docker inspect --format",
|
|
"set_runtime_image \"$previous_image\"",
|
|
"\"${compose[@]}\" up -d --no-build --force-recreate dobotai-dingtalk",
|
|
} {
|
|
if !strings.Contains(source, required) {
|
|
t.Errorf("start script missing container upgrade rollback %q", required)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDobotAIDingTalkDockerInstallerPreparesDirectoriesDatabaseAndMigrations(t *testing.T) {
|
|
data, err := os.ReadFile("../deploy/dobotai-dingtalk/install-docker.sh")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, required := range []string{
|
|
"container", "secrets", "backups", "migrations", "postgres.env",
|
|
"dobotai-postgres", "apply-migrations.sh", "docker compose", "umask 077",
|
|
} {
|
|
if !strings.Contains(source, required) {
|
|
t.Errorf("docker installer missing %q", required)
|
|
}
|
|
}
|
|
if strings.Contains(source, "dobotai-dingtalk up -d") {
|
|
t.Fatal("installer must prepare database but must not start the unconfigured application")
|
|
}
|
|
}
|
|
|
|
func TestDobotAIDingTalkExistingDatabaseMigrationHasBackupAndRollbackBoundary(t *testing.T) {
|
|
data, err := os.ReadFile("../deploy/dobotai-dingtalk/migrate-existing-database.sh")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, required := range []string{
|
|
"pg_dump -Fc", "pg_restore --list", "dobotai-postgres-data",
|
|
"dobotai-dingtalk", "rollback", "apply-migrations.sh",
|
|
} {
|
|
if !strings.Contains(source, required) {
|
|
t.Errorf("database adoption script missing %q", required)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRootDockerInstallerIsCloneAndRunEntryPoint(t *testing.T) {
|
|
data, err := os.ReadFile("../install-docker.sh")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, required := range []string{
|
|
"docker build",
|
|
"dobotai-dingtalk:local-",
|
|
"install_docker_if_missing",
|
|
"docker compose version",
|
|
"DOBOTAI_SOURCE_ROOT",
|
|
"deploy/dobotai-dingtalk/install-docker.sh",
|
|
} {
|
|
if !strings.Contains(source, required) {
|
|
t.Errorf("root installer missing %q", required)
|
|
}
|
|
}
|
|
if strings.Contains(source, "${DOBOTAI_IMAGE") || strings.Contains(source, "DOBOTAI_IMAGE=?") {
|
|
t.Fatal("root installer must not require an operator supplied DOBOTAI_IMAGE")
|
|
}
|
|
}
|
|
|
|
func TestRootDockerInstallerBuildsWithInternalGoModuleProxy(t *testing.T) {
|
|
installer, err := os.ReadFile("../install-docker.sh")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dockerfile, err := os.ReadFile("../Dockerfile")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(installer), "GOPROXY=https://goproxy.cn,direct") {
|
|
t.Fatal("root installer must pass an internal default GOPROXY to docker build")
|
|
}
|
|
if !strings.Contains(string(dockerfile), "ARG GOPROXY") || !strings.Contains(string(dockerfile), "GOPROXY=$GOPROXY go mod download") {
|
|
t.Fatal("Dockerfile must accept and use the root installer GOPROXY")
|
|
}
|
|
}
|