Newer
Older
EnvoyControlPlane / internal / pkg / snapshot / resource_io_test.go
package snapshot

import (
	"context"
	"testing"
)

// NOTE: Assume MockLogger and SnapshotManager are defined for the test to run.
// The actual implementation of LoadFilterChainFromYAML is assumed to be available
// to the test file.

// TestLoadFilterChainFromYAML_ComplexInput tests the functionality of LoadFilterChainFromYAML
func TestLoadFilterChainFromYAML_ComplexInput(t *testing.T) {
	ctx := context.Background()

	// The user's provided, valid YAML for a single FilterChain object
	validComplexYAML := `
    filters:
    - name: envoy.filters.network.http_connection_manager
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
        stat_prefix: ingress_http
        codec_type: AUTO
        upgrade_configs:
        - upgrade_type: websocket
        stream_idle_timeout: 0s
        normalize_path: true
        merge_slashes: true
        route_config:
          virtual_hosts:
          - name: printer_service
            domains: ["printer.jerxie.com"]
            routes:
            - match: { prefix: "/webcam" }
              route: { prefix_rewrite: "/", cluster: "_3d_printer_camera", max_stream_duration: {grpc_timeout_header_max: 0s} }
            - match: { prefix: "/" }
              route: { cluster: "_3d_printer_console"}
        http_filters:
        - name: envoy.filters.http.oauth2
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2
            config:
              token_endpoint:
                cluster: _auth_server
                uri: auth.jerxie.com/token
                timeout: 3s
              authorization_endpoint: https://auth.jerxie.com/auth
              redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback"
              redirect_path_matcher:
                path:
                  exact: /callback
              signout_path:
                path:
                  exact: /signout
              forward_bearer_token: true
              credentials:
                client_id: octoprint-portal
                token_secret:
                  name: token
                  sds_config:
                    path: "/etc/envoy/token-secret.yaml"
                hmac_secret:
                  name: hmac
                  sds_config:
                    path: "/etc/envoy/hmac-secret.yaml"
              auth_scopes:
              - openid
              - email
        - name: envoy.filters.http.jwt_authn
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
            providers:
              provider1:
                remote_jwks:
                  http_uri:
                    uri: "https://auth.jerxie.com/keys"
                    cluster: _auth_server
                    timeout: 5s
                  cache_duration: 600s
                from_headers:
                - name: Authorization
                  value_prefix: "Bearer "
                payload_in_metadata: jwt_payload
            rules:
              - match:
                  prefix: /
                requires:
                  provider_name: provider1
        - name: envoy.filters.http.lua
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inline_code: |
              email = ""
              function envoy_on_request(request_handle)
                email = ""
                local meta = request_handle:streamInfo():dynamicMetadata()
                for key, value in pairs(meta:get("envoy.filters.http.jwt_authn")) do
                  if key == "jwt_payload" then
                    for k, v in pairs(value) do
                      if k == "email" then
                        print("login octoprint: "..v)
                        email = v
                        request_handle:headers():add("ENVOY_AUTHENTICATED_USER", v)
                      end
                    end
                  end
                end
              end

              function envoy_on_response(response_handle)
                if email ~="" and email ~= "axieyangb@gmail.com" then
                  response_handle:logInfo("Got unauthorized user, return 403 for user " ..email)
                  response_handle:headers():add("set-cookie", "BearerToken=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
                  response_handle:headers():add("set-cookie", "OauthHMAC=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
                  response_handle:headers():add("set-cookie", "IdToken=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
                  response_handle:headers():add("set-cookie", "OauthExpires=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
                end
                email = ""
              end
        - name: envoy.filters.http.router
          typed_config:                  
            "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
    filter_chain_match:
      server_names: ["printer.jerxie.com", "printer.local"]
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
        common_tls_context:
          tls_certificates:
          - certificate_chain: { filename: "/etc/certs/downstream/printer.jerxie.com/fullchain.pem" }
            private_key: { filename: "/etc/certs/downstream/printer.jerxie.com/privkey.pem" }
`

	tests := []struct {
		name        string
		yamlStr     string
		expectError bool
		expectedLen int // Expected number of network filters (top-level filters array)
	}{
		{
			name:        "Success_ComplexSingleFilterChain",
			yamlStr:     validComplexYAML,
			expectError: false,
			expectedLen: 1, // Only one top-level network filter: http_connection_manager
		},
		// Re-include sanity checks for robust testing
		{
			name:        "Error_NoFiltersInChain",
			yamlStr:     `filter_chain_match: { server_names: ["empty"] }`,
			expectError: true,
			expectedLen: 0,
		},
		{
			name:        "Error_InputIsAList",
			yamlStr:     `- filters: []`,
			expectError: true, // Should fail unmarshaling a list into a single struct
			expectedLen: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			chain, err := LoadFilterChainFromYAML(ctx, tt.yamlStr)

			if tt.expectError {
				if err == nil {
					t.Errorf("Expected an error but got nil")
				}
				if chain != nil {
					t.Errorf("Expected nil chain on error, but got non-nil")
				}
			} else {
				if err != nil {
					t.Fatalf("Expected no error but got: %v", err)
				}
				if chain == nil {
					t.Fatal("Expected non-nil filter chain, but got nil")
				}

				// 1. Check top-level filter count
				if len(chain.Filters) != tt.expectedLen {
					t.Errorf("Top-level filter count mismatch. Got %d, want %d", len(chain.Filters), tt.expectedLen)
				}

				// 2. Check a deeply nested value to ensure complex unmarshaling worked
				if len(chain.FilterChainMatch.ServerNames) == 0 || chain.FilterChainMatch.ServerNames[0] != "printer.jerxie.com" {
					t.Errorf("FilterChainMatch assertion failed. Expected server name 'printer.jerxie.com'")
				}

				// 3. Check the name of the top-level filter
				if chain.Filters[0].Name != "envoy.filters.network.http_connection_manager" {
					t.Errorf("Top-level filter name mismatch. Got %s", chain.Filters[0].Name)
				}
			}
		})
	}
}