lolly/internal/converter/nginx/converter_test.go
xfy 2734b04d8f refactor: remove 16.8k lines of dead code across all internal packages
- Delete unused files: tempfile subsystem, matcher variants, server/internal
- Remove 200+ unused functions across proxy, ssl, lua, http2/3, stream, variable
- Fix proxy test type errors (backgroundRefresh ctx→Request)
- Move bench/tools mock backend into internal/testutil
- Remove corresponding test functions for all deleted code
2026-06-03 16:15:43 +08:00

1436 lines
31 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package nginx
import (
"fmt"
"strings"
"testing"
"time"
"rua.plus/lolly/internal/config"
)
// helper: parse nginx config string and convert.
func convertString(t *testing.T, input string) (*ConvertResult, error) {
t.Helper()
cfg, err := ParseString(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 TestConvertServerLevelRoot(t *testing.T) {
input := `
http {
server {
listen 80;
root /var/www/html;
index index.html index.htm index.php;
}
}
`
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 != "/" {
t.Errorf("expected path /, 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) != 3 || st.Index[0] != "index.html" || st.Index[1] != "index.htm" || st.Index[2] != "index.php" {
t.Errorf("expected [index.html index.htm index.php], got %v", st.Index)
}
}
func TestConvertServerLevelRootWithLocation(t *testing.T) {
// When both server-level root and location /static exist, both should be converted
input := `
http {
server {
listen 80;
root /var/www/html;
index index.html;
location /static/ {
root /var/www/static;
}
}
}
`
result, err := convertString(t, input)
if err != nil {
t.Fatalf("convert error: %v", err)
}
s := result.Config.Servers[0]
if len(s.Static) != 2 {
t.Fatalf("expected 2 static, got %d", len(s.Static))
}
// Find the root location
var rootStatic, staticLoc *config.StaticConfig
for i := range s.Static {
if s.Static[i].Path == "/" {
rootStatic = &s.Static[i]
} else if s.Static[i].Path == "/static/" {
staticLoc = &s.Static[i]
}
}
if rootStatic == nil {
t.Error("expected root static config at path /")
} else if rootStatic.Root != "/var/www/html" {
t.Errorf("expected root /var/www/html, got %s", rootStatic.Root)
}
if staticLoc == nil {
t.Error("expected static config at path /static/")
} else if staticLoc.Root != "/var/www/static" {
t.Errorf("expected root /var/www/static, got %s", staticLoc.Root)
}
}
func TestConvertServerLevelRootNotCreatedForProxy(t *testing.T) {
// When location / is a proxy, server-level root should NOT create a static config
input := `
http {
server {
listen 80;
root /var/www/html;
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.Static) != 0 {
t.Errorf("expected 0 static (location / is proxy), got %d", len(s.Static))
}
if len(s.Proxy) != 1 {
t.Errorf("expected 1 proxy, got %d", len(s.Proxy))
}
}
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 != "http://10.0.0.1:8080" {
t.Errorf("target[0] URL = %s, want http://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.Alias != "/data/photos/" {
t.Errorf("Alias = %s, want /data/photos/", st.Alias)
}
// alias 和 root 互斥root 应为空
if st.Root != "" {
t.Errorf("Root = %s, want empty (alias is set)", st.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 != "http://10.0.0.1:8080" {
t.Errorf("target[0] URL = %s, want http://10.0.0.1:8080", p.Targets[0].URL)
}
if p.Targets[1].URL != "http://10.0.0.2:8080" {
t.Errorf("target[1] URL = %s, want http://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)
}
}