- 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
1436 lines
31 KiB
Go
1436 lines
31 KiB
Go
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)
|
||
}
|
||
}
|