lolly/internal/converter/nginx/converter_test.go
xfy 909bd405d2 feat(converter,app): 添加 nginx 配置导入功能
- 新增 internal/converter/nginx 解析器和转换器
- main.go 添加 --import/-i 参数支持 nginx 配置导入
- app_test.go 添加导入功能相关测试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 17:12:49 +08:00

1329 lines
29 KiB
Go

package nginx
import (
"fmt"
"strings"
"testing"
"time"
)
// helper: parse nginx config string and convert.
func convertString(t *testing.T, input string) (*ConvertResult, error) {
t.Helper()
cfg, err := Parse(input)
if err != nil {
t.Fatalf("parse error: %v", err)
}
return Convert(cfg)
}
// helper: check if any warning contains substring.
func hasWarningContaining(warnings []Warning, substr string) bool {
for _, w := range warnings {
if strings.Contains(w.Message, substr) {
return true
}
}
return false
}
func TestConvertServerBlock(t *testing.T) {
input := `
http {
server {
listen 8080;
server_name example.com www.example.com;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if len(result.Config.Servers) != 1 {
t.Fatalf("expected 1 server, got %d", len(result.Config.Servers))
}
s := result.Config.Servers[0]
if s.Listen != ":8080" {
t.Errorf("expected listen :8080, got %s", s.Listen)
}
if s.Name != "example.com" {
t.Errorf("expected name example.com, got %s", s.Name)
}
if len(s.ServerNames) != 2 {
t.Fatalf("expected 2 server_names, got %d", len(s.ServerNames))
}
if s.ServerNames[0] != "example.com" || s.ServerNames[1] != "www.example.com" {
t.Errorf("expected [example.com www.example.com], got %v", s.ServerNames)
}
}
func TestConvertLocationProxyPass(t *testing.T) {
input := `
http {
server {
listen 80;
location /api/ {
proxy_pass http://backend:8080;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
p := s.Proxy[0]
if p.Path != "/api/" {
t.Errorf("expected path /api/, got %s", p.Path)
}
if len(p.Targets) != 1 {
t.Fatalf("expected 1 target, got %d", len(p.Targets))
}
if p.Targets[0].URL != "http://backend:8080" {
t.Errorf("expected target URL http://backend:8080, got %s", p.Targets[0].URL)
}
}
func TestConvertLocationRoot(t *testing.T) {
input := `
http {
server {
listen 80;
location /static/ {
root /var/www/html;
index index.html index.htm;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Static) != 1 {
t.Fatalf("expected 1 static, got %d", len(s.Static))
}
st := s.Static[0]
if st.Path != "/static/" {
t.Errorf("expected path /static/, got %s", st.Path)
}
if st.Root != "/var/www/html" {
t.Errorf("expected root /var/www/html, got %s", st.Root)
}
if len(st.Index) != 2 || st.Index[0] != "index.html" || st.Index[1] != "index.htm" {
t.Errorf("expected [index.html index.htm], got %v", st.Index)
}
}
func TestConvertUpstream(t *testing.T) {
input := `
upstream backend {
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 backup;
}
http {
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
p := s.Proxy[0]
if len(p.Targets) != 3 {
t.Fatalf("expected 3 targets, got %d", len(p.Targets))
}
if p.Targets[0].URL != "10.0.0.1:8080" {
t.Errorf("target[0] URL = %s, want 10.0.0.1:8080", p.Targets[0].URL)
}
if p.Targets[0].Weight != 3 {
t.Errorf("target[0] Weight = %d, want 3", p.Targets[0].Weight)
}
if p.Targets[1].MaxFails != 3 {
t.Errorf("target[1] MaxFails = %d, want 3", p.Targets[1].MaxFails)
}
if p.Targets[1].FailTimeout != 30*time.Second {
t.Errorf("target[1] FailTimeout = %v, want 30s", p.Targets[1].FailTimeout)
}
if !p.Targets[2].Backup {
t.Error("target[2] Backup = false, want true")
}
}
func TestConvertLocationModifiers(t *testing.T) {
tests := []struct {
name string
input string
wantType string
}{
{
name: "exact",
input: `
http {
server {
listen 80;
location = /exact {
proxy_pass http://backend;
}
}
}`,
wantType: "exact",
},
{
name: "prefix_priority",
input: `
http {
server {
listen 80;
location ^~ /prefix {
proxy_pass http://backend;
}
}
}`,
wantType: "prefix_priority",
},
{
name: "regex",
input: `
http {
server {
listen 80;
location ~ \.php$ {
proxy_pass http://backend;
}
}
}`,
wantType: "regex",
},
{
name: "regex_caseless",
input: `
http {
server {
listen 80;
location ~* \.php$ {
proxy_pass http://backend;
}
}
}`,
wantType: "regex_caseless",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := convertString(t, tt.input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
if s.Proxy[0].LocationType != tt.wantType {
t.Errorf("LocationType = %s, want %s", s.Proxy[0].LocationType, tt.wantType)
}
})
}
}
func TestConvertGzipConfig(t *testing.T) {
input := `
http {
server {
listen 80;
gzip on;
gzip_types text/plain text/css application/json;
gzip_min_length 1024;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.Compression.Type != "gzip" {
t.Errorf("Compression.Type = %s, want gzip", s.Compression.Type)
}
if len(s.Compression.Types) != 3 {
t.Fatalf("Compression.Types length = %d, want 3", len(s.Compression.Types))
}
if s.Compression.Types[0] != "text/plain" {
t.Errorf("Compression.Types[0] = %s, want text/plain", s.Compression.Types[0])
}
if s.Compression.MinSize != 1024 {
t.Errorf("Compression.MinSize = %d, want 1024", s.Compression.MinSize)
}
}
func TestConvertRewrite(t *testing.T) {
input := `
http {
server {
listen 80;
rewrite ^/old/(.*)$ /new/$1 last;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Rewrite) != 1 {
t.Fatalf("expected 1 rewrite rule, got %d", len(s.Rewrite))
}
r := s.Rewrite[0]
if r.Pattern != "^/old/(.*)$" {
t.Errorf("Pattern = %s, want ^/old/(.*)$", r.Pattern)
}
if r.Replacement != "/new/$1" {
t.Errorf("Replacement = %s, want /new/$1", r.Replacement)
}
if r.Flag != "last" {
t.Errorf("Flag = %s, want last", r.Flag)
}
}
func TestConvertReturn301(t *testing.T) {
input := `
http {
server {
listen 80;
location /old {
return 301 https://example.com/new;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Rewrite) != 1 {
t.Fatalf("expected 1 rewrite rule, got %d", len(s.Rewrite))
}
r := s.Rewrite[0]
if r.Pattern != "^/old$" {
t.Errorf("Pattern = %s, want ^/old$", r.Pattern)
}
if r.Replacement != "https://example.com/new" {
t.Errorf("Replacement = %s, want https://example.com/new", r.Replacement)
}
if r.Flag != "permanent" {
t.Errorf("Flag = %s, want permanent", r.Flag)
}
if !hasWarningContaining(result.Warnings, "return 301") {
t.Error("expected warning about return 301 conversion")
}
}
func TestConvertReturn302(t *testing.T) {
input := `
http {
server {
listen 80;
location /temp {
return 302 https://example.com/temp-new;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Rewrite) != 1 {
t.Fatalf("expected 1 rewrite rule, got %d", len(s.Rewrite))
}
r := s.Rewrite[0]
if r.Flag != "redirect" {
t.Errorf("Flag = %s, want redirect", r.Flag)
}
if !hasWarningContaining(result.Warnings, "return 302") {
t.Error("expected warning about return 302 conversion")
}
}
func TestConvertSSLConfig(t *testing.T) {
input := `
http {
server {
listen 443 ssl;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.SSL.Cert != "/etc/ssl/server.crt" {
t.Errorf("SSL.Cert = %s, want /etc/ssl/server.crt", s.SSL.Cert)
}
if s.SSL.Key != "/etc/ssl/server.key" {
t.Errorf("SSL.Key = %s, want /etc/ssl/server.key", s.SSL.Key)
}
if s.Listen != ":443" {
t.Errorf("Listen = %s, want :443", s.Listen)
}
}
func TestConvertProxyAddXForwardedFor(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
val := s.Proxy[0].Headers.SetRequest["X-Forwarded-For"]
if val != "$remote_addr" {
t.Errorf("X-Forwarded-For = %s, want $remote_addr", val)
}
if !hasWarningContaining(result.Warnings, "$proxy_add_x_forwarded_for") {
t.Error("expected warning about $proxy_add_x_forwarded_for replacement")
}
}
func TestConvertUnsupportedDirective(t *testing.T) {
input := `
http {
server {
listen 80;
if ($host = example.com) {
return 301 https://example.com;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if !hasWarningContaining(result.Warnings, "'if' directive") {
t.Error("expected warning about unsupported 'if' directive")
}
}
func TestConvertEmptyLocation(t *testing.T) {
input := `
http {
server {
listen 80;
location /empty {
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if !hasWarningContaining(result.Warnings, "no content") {
t.Error("expected warning about empty location")
}
}
func TestConvertConflictingLocation(t *testing.T) {
input := `
http {
server {
listen 80;
location /conflict {
proxy_pass http://backend;
root /var/www;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
// Should be classified as proxy, not static.
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
if len(s.Static) != 0 {
t.Errorf("expected 0 static, got %d", len(s.Static))
}
if !hasWarningContaining(result.Warnings, "proxy_pass takes priority") {
t.Error("expected warning about proxy_pass taking priority over root/alias")
}
}
func TestConvertAliasDirective(t *testing.T) {
input := `
http {
server {
listen 80;
location /images/ {
alias /data/photos/;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Static) != 1 {
t.Fatalf("expected 1 static, got %d", len(s.Static))
}
st := s.Static[0]
if st.Root != "/data/photos/" {
t.Errorf("Root = %s, want /data/photos/", st.Root)
}
if !hasWarningContaining(result.Warnings, "alias") {
t.Error("expected warning about alias conversion to root")
}
}
func TestConvertReturnNonRedirect(t *testing.T) {
input := `
http {
server {
listen 80;
location /health {
return 200 "OK";
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if len(result.Warnings) == 0 {
t.Fatal("expected at least one warning, got none")
}
if !hasWarningContaining(result.Warnings, "'return' directive") && !hasWarningContaining(result.Warnings, "return") {
t.Errorf("expected warning about non-redirect return, got warnings: %v", result.Warnings)
}
}
func TestConvertServerLevelReturn(t *testing.T) {
input := `
http {
server {
listen 80;
return 301 https://example.com;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Rewrite) != 1 {
t.Fatalf("expected 1 rewrite rule, got %d", len(s.Rewrite))
}
r := s.Rewrite[0]
if r.Pattern != "^/" {
t.Errorf("Pattern = %s, want ^/", r.Pattern)
}
if r.Replacement != "https://example.com" {
t.Errorf("Replacement = %s, want https://example.com", r.Replacement)
}
if r.Flag != "permanent" {
t.Errorf("Flag = %s, want permanent", r.Flag)
}
}
func TestConvertProxySetHeader(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
p := s.Proxy[0]
if p.Headers.SetRequest["Host"] != "$host" {
t.Errorf("Host = %s, want $host", p.Headers.SetRequest["Host"])
}
if p.Headers.SetRequest["X-Real-IP"] != "$remote_addr" {
t.Errorf("X-Real-IP = %s, want $remote_addr", p.Headers.SetRequest["X-Real-IP"])
}
if p.Headers.SetRequest["X-Forwarded-Proto"] != "$scheme" {
t.Errorf("X-Forwarded-Proto = %s, want $scheme", p.Headers.SetRequest["X-Forwarded-Proto"])
}
}
func TestConvertProxyTimeout(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 15s;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
p := s.Proxy[0]
if p.Timeout.Connect != 5*time.Second {
t.Errorf("Connect = %v, want 5s", p.Timeout.Connect)
}
if p.Timeout.Read != 30*time.Second {
t.Errorf("Read = %v, want 30s", p.Timeout.Read)
}
if p.Timeout.Write != 15*time.Second {
t.Errorf("Write = %v, want 15s", p.Timeout.Write)
}
}
func TestConvertClientMaxBodySize(t *testing.T) {
input := `
http {
server {
listen 80;
client_max_body_size 10m;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.ClientMaxBodySize != "10m" {
t.Errorf("ClientMaxBodySize = %s, want 10m", s.ClientMaxBodySize)
}
}
func TestConvertMultipleServerBlocks(t *testing.T) {
input := `
http {
server {
listen 80;
server_name example.com;
}
server {
listen 443 ssl;
server_name secure.example.com;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if len(result.Config.Servers) != 2 {
t.Fatalf("expected 2 servers, got %d", len(result.Config.Servers))
}
if result.Config.Servers[0].Listen != ":80" {
t.Errorf("server[0] Listen = %s, want :80", result.Config.Servers[0].Listen)
}
if result.Config.Servers[1].Listen != ":443" {
t.Errorf("server[1] Listen = %s, want :443", result.Config.Servers[1].Listen)
}
if result.Config.Servers[1].Name != "secure.example.com" {
t.Errorf("server[1] Name = %s, want secure.example.com", result.Config.Servers[1].Name)
}
}
func TestConvertDefaultServer(t *testing.T) {
input := `
http {
server {
listen 80 default_server;
server_name _;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if !s.Default {
t.Error("Default = false, want true")
}
if s.Listen != ":80" {
t.Errorf("Listen = %s, want :80", s.Listen)
}
}
func TestConvertProxyRedirect(t *testing.T) {
tests := []struct {
name string
input string
mode string
rules int
}{
{
name: "off",
input: `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_redirect off;
}
}
}`,
mode: "off",
rules: 0,
},
{
name: "default",
input: `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_redirect default;
}
}
}`,
mode: "default",
rules: 0,
},
{
name: "custom",
input: `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_redirect http://backend/ /;
}
}
}`,
mode: "custom",
rules: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := convertString(t, tt.input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if p.RedirectRewrite == nil {
t.Fatal("RedirectRewrite is nil")
}
if p.RedirectRewrite.Mode != tt.mode {
t.Errorf("Mode = %s, want %s", p.RedirectRewrite.Mode, tt.mode)
}
if len(p.RedirectRewrite.Rules) != tt.rules {
t.Errorf("Rules count = %d, want %d", len(p.RedirectRewrite.Rules), tt.rules)
}
})
}
}
func TestConvertErrorPage(t *testing.T) {
input := `
http {
server {
listen 80;
error_page 404 /404.html;
error_page 500 502 503 /50x.html;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
pages := s.Security.ErrorPage.Pages
if pages[404] != "/404.html" {
t.Errorf("error_page[404] = %s, want /404.html", pages[404])
}
if pages[500] != "/50x.html" {
t.Errorf("error_page[500] = %s, want /50x.html", pages[500])
}
if pages[502] != "/50x.html" {
t.Errorf("error_page[502] = %s, want /50x.html", pages[502])
}
if pages[503] != "/50x.html" {
t.Errorf("error_page[503] = %s, want /50x.html", pages[503])
}
}
func TestConvertAuthBasic(t *testing.T) {
input := `
http {
server {
listen 80;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.Security.Auth.Type != "basic" {
t.Errorf("Auth.Type = %s, want basic", s.Security.Auth.Type)
}
if s.Security.Auth.Realm != "Restricted Area" {
t.Errorf("Auth.Realm = %s, want Restricted Area", s.Security.Auth.Realm)
}
if !hasWarningContaining(result.Warnings, "auth_basic_user_file") {
t.Error("expected warning about auth_basic_user_file migration")
}
}
func TestConvertTopLevelServer(t *testing.T) {
// Server block without http wrapper.
input := `
server {
listen 8080;
server_name direct.example.com;
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if len(result.Config.Servers) != 1 {
t.Fatalf("expected 1 server, got %d", len(result.Config.Servers))
}
s := result.Config.Servers[0]
if s.Listen != ":8080" {
t.Errorf("Listen = %s, want :8080", s.Listen)
}
if s.Name != "direct.example.com" {
t.Errorf("Name = %s, want direct.example.com", s.Name)
}
}
func TestConvertLoggingConfig(t *testing.T) {
input := `
http {
server {
listen 80;
access_log /var/log/nginx/access.log combined;
error_log /var/log/nginx/error.log warn;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if result.Config.Logging.Access.Path != "/var/log/nginx/access.log" {
t.Errorf("Access.Path = %s, want /var/log/nginx/access.log", result.Config.Logging.Access.Path)
}
if result.Config.Logging.Access.Format != "combined" {
t.Errorf("Access.Format = %s, want combined", result.Config.Logging.Access.Format)
}
if result.Config.Logging.Error.Path != "/var/log/nginx/error.log" {
t.Errorf("Error.Path = %s, want /var/log/nginx/error.log", result.Config.Logging.Error.Path)
}
if result.Config.Logging.Error.Level != "warn" {
t.Errorf("Error.Level = %s, want warn", result.Config.Logging.Error.Level)
}
}
func TestConvertServerTokens(t *testing.T) {
input := `
http {
server {
listen 80;
server_tokens off;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.ServerTokens {
t.Error("ServerTokens = true, want false")
}
}
func TestConvertUpstreamLoadBalance(t *testing.T) {
tests := []struct {
name string
upstream string
wantLB string
}{
{
name: "least_conn",
upstream: "least_conn;",
wantLB: "least_conn",
},
{
name: "ip_hash",
upstream: "ip_hash;",
wantLB: "ip_hash",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := fmt.Sprintf(`
upstream backend {
server 10.0.0.1:8080;
%s
}
http {
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
`, tt.upstream)
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if p.LoadBalance != tt.wantLB {
t.Errorf("LoadBalance = %s, want %s", p.LoadBalance, tt.wantLB)
}
})
}
}
func TestConvertTryFiles(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
root /var/www;
try_files $uri $uri/ /index.html;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
st := s.Static[0]
if len(st.TryFiles) != 3 {
t.Fatalf("TryFiles length = %d, want 3", len(st.TryFiles))
}
if st.TryFiles[0] != "$uri" || st.TryFiles[1] != "$uri/" || st.TryFiles[2] != "/index.html" {
t.Errorf("TryFiles = %v, want [$uri $uri/ /index.html]", st.TryFiles)
}
}
func TestConvertProxyHidePassHeaders(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_hide_header X-Powered-By;
proxy_pass_header X-My-Custom;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if len(p.Headers.HideResponse) != 1 || p.Headers.HideResponse[0] != "X-Powered-By" {
t.Errorf("HideResponse = %v, want [X-Powered-By]", p.Headers.HideResponse)
}
if len(p.Headers.PassResponse) != 1 || p.Headers.PassResponse[0] != "X-My-Custom" {
t.Errorf("PassResponse = %v, want [X-My-Custom]", p.Headers.PassResponse)
}
}
func TestConvertProxyCache(t *testing.T) {
input := `
http {
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if !p.Cache.Enabled {
t.Error("Cache.Enabled = false, want true")
}
if p.CacheValid == nil {
t.Fatal("CacheValid is nil")
}
if p.CacheValid.OK != 10*time.Minute {
t.Errorf("CacheValid.OK = %v, want 10m", p.CacheValid.OK)
}
if p.CacheValid.NotFound != time.Minute {
t.Errorf("CacheValid.NotFound = %v, want 1m", p.CacheValid.NotFound)
}
}
func TestConvertNamedLocation(t *testing.T) {
input := `
http {
server {
listen 80;
location @fallback {
proxy_pass http://fallback-backend;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if p.LocationType != "named" {
t.Errorf("LocationType = %s, want named", p.LocationType)
}
if p.LocationName != "fallback" {
t.Errorf("LocationName = %s, want fallback", p.LocationName)
}
}
func TestConvertWarningString(t *testing.T) {
w := Warning{
Directive: "if",
Line: 10,
File: "test.conf",
Message: "unsupported",
}
s := w.String()
if !strings.Contains(s, "test.conf:10") {
t.Errorf("Warning.String() = %s, should contain test.conf:10", s)
}
if !strings.Contains(s, "unsupported") {
t.Errorf("Warning.String() = %s, should contain 'unsupported'", s)
}
}
func TestConvertEmptyConfig(t *testing.T) {
input := ``
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if len(result.Config.Servers) != 0 {
t.Errorf("expected 0 servers, got %d", len(result.Config.Servers))
}
}
func TestConvertServerTokensOn(t *testing.T) {
input := `
http {
server {
listen 80;
server_tokens on;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if !s.ServerTokens {
t.Error("ServerTokens = false, want true")
}
}
func TestConvertMapDirectiveWarning(t *testing.T) {
input := `
http {
map $host $backend {
default backend1;
}
server {
listen 80;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
if !hasWarningContaining(result.Warnings, "'map' directive") {
t.Error("expected warning about unsupported 'map' directive")
}
}
func TestConvertUpstreamDownServer(t *testing.T) {
input := `
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080 down;
}
http {
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
p := result.Config.Servers[0].Proxy[0]
if len(p.Targets) != 2 {
t.Fatalf("expected 2 targets, got %d", len(p.Targets))
}
if !p.Targets[1].Down {
t.Error("target[1].Down = false, want true")
}
}
func TestConvertUpstreamInsideHttpBlock(t *testing.T) {
// Upstream blocks inside http block should be found.
input := `
http {
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Proxy) != 1 {
t.Fatalf("expected 1 proxy, got %d", len(s.Proxy))
}
p := s.Proxy[0]
if len(p.Targets) != 2 {
t.Fatalf("expected 2 targets from upstream inside http block, got %d", len(p.Targets))
}
if p.Targets[0].URL != "10.0.0.1:8080" {
t.Errorf("target[0] URL = %s, want 10.0.0.1:8080", p.Targets[0].URL)
}
if p.Targets[1].URL != "10.0.0.2:8080" {
t.Errorf("target[1] URL = %s, want 10.0.0.2:8080", p.Targets[1].URL)
}
}
func TestConvertSSLWithoutCertKey(t *testing.T) {
// SSL enabled via listen directive but no ssl_certificate/key should produce a warning.
input := `
http {
server {
listen 443 ssl;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.SSL.Cert != "" {
t.Errorf("SSL.Cert = %s, want empty string (no auto)", s.SSL.Cert)
}
if s.SSL.Key != "" {
t.Errorf("SSL.Key = %s, want empty string (no auto)", s.SSL.Key)
}
if !hasWarningContaining(result.Warnings, "ssl_certificate") {
t.Error("expected warning about missing ssl_certificate/ssl_certificate_key")
}
}
func TestConvertSSLWithCertKeyNoWarning(t *testing.T) {
// SSL enabled with both cert and key should NOT produce a warning.
input := `
http {
server {
listen 443 ssl;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.SSL.Cert != "/etc/ssl/server.crt" {
t.Errorf("SSL.Cert = %s, want /etc/ssl/server.crt", s.SSL.Cert)
}
if s.SSL.Key != "/etc/ssl/server.key" {
t.Errorf("SSL.Key = %s, want /etc/ssl/server.key", s.SSL.Key)
}
if hasWarningContaining(result.Warnings, "ssl_certificate") {
t.Error("unexpected warning about missing ssl_certificate when both are provided")
}
}
func TestConvertServerNoListenDefault(t *testing.T) {
// Server block without listen directive should get default 0.0.0.0:80.
input := `
http {
server {
server_name example.com;
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if s.Listen != "0.0.0.0:80" {
t.Errorf("Listen = %s, want 0.0.0.0:80 when no listen directive", s.Listen)
}
}