// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

//go:build !plan9

package conf

import (
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"tailscale.com/types/ptr"
)

// Test that the config file can be at the root of the object, or in a versioned sub-object.
// or {"version": "v1beta1", "a-beta-config": "a-beta-value", "v1alpha1": {"authKey": "abc123"}}
func TestVersionedConfig(t *testing.T) {
	testCases := map[string]struct {
		inputConfig    string
		expectedConfig ConfigV1Alpha1
		expectedError  string
	}{
		"root_config_v1alpha1": {
			inputConfig:    `{"version": "v1alpha1", "authKey": "abc123"}`,
			expectedConfig: ConfigV1Alpha1{AuthKey: ptr.To("abc123")},
		},
		"backwards_compat_v1alpha1_config": {
			// Client doesn't know about v1beta1, so it should read in v1alpha1.
			inputConfig:    `{"version": "v1beta1", "beta-key": "beta-value", "authKey": "def456", "v1alpha1": {"authKey": "abc123"}}`,
			expectedConfig: ConfigV1Alpha1{AuthKey: ptr.To("abc123")},
		},
		"unknown_key_allowed": {
			// Adding new keys to the config doesn't require a version bump.
			inputConfig:    `{"version": "v1alpha1", "unknown-key": "unknown-value", "authKey": "abc123"}`,
			expectedConfig: ConfigV1Alpha1{AuthKey: ptr.To("abc123")},
		},
		"version_only_no_authkey": {
			inputConfig:    `{"version": "v1alpha1"}`,
			expectedConfig: ConfigV1Alpha1{},
		},
		"both_config_v1alpha1": {
			inputConfig:   `{"version": "v1alpha1", "authKey": "abc123", "v1alpha1": {"authKey": "def456"}}`,
			expectedError: "both root and v1alpha1 config provided",
		},
		"empty_config": {
			inputConfig:   `{}`,
			expectedError: `no "version" field provided`,
		},
		"v1beta1_without_backwards_compat": {
			inputConfig:   `{"version": "v1beta1", "beta-key": "beta-value", "authKey": "def456"}`,
			expectedError: `unsupported "version" value "v1beta1"; want "v1alpha1"`,
		},
	}

	for name, tc := range testCases {
		t.Run(name, func(t *testing.T) {
			cfg, err := Load([]byte(tc.inputConfig))
			switch {
			case tc.expectedError == "" && err != nil:
				t.Fatalf("unexpected error: %v", err)
			case tc.expectedError != "":
				if err == nil {
					t.Fatalf("expected error %q, got nil", tc.expectedError)
				} else if !strings.Contains(err.Error(), tc.expectedError) {
					t.Fatalf("expected error %q, got %q", tc.expectedError, err.Error())
				}
				return
			}
			if cfg.Version != "v1alpha1" {
				t.Fatalf("expected version %q, got %q", "v1alpha1", cfg.Version)
			}
			// Diff actual vs expected config.
			if diff := cmp.Diff(cfg.Parsed, tc.expectedConfig); diff != "" {
				t.Fatalf("Unexpected parsed config (-got +want):\n%s", diff)
			}
		})
	}
}
