github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/filevar/filevar_test.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package filevar
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"io/ioutil"
    21  	"net/url"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"gocloud.dev/runtimevar"
    30  	"gocloud.dev/runtimevar/driver"
    31  	"gocloud.dev/runtimevar/drivertest"
    32  	"gocloud.dev/secrets"
    33  	_ "gocloud.dev/secrets/localsecrets"
    34  )
    35  
    36  type harness struct {
    37  	dir    string
    38  	closer func()
    39  }
    40  
    41  func newHarness(t *testing.T) (drivertest.Harness, error) {
    42  	dir, err := ioutil.TempDir("", "filevar_test-")
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	return &harness{
    47  		dir:    dir,
    48  		closer: func() { _ = os.RemoveAll(dir) },
    49  	}, nil
    50  }
    51  
    52  func (h *harness) MakeWatcher(ctx context.Context, name string, decoder *runtimevar.Decoder) (driver.Watcher, error) {
    53  	// filevar uses a goroutine in the background that poll every WaitDuration if
    54  	// the file is deleted. Make this fast for tests.
    55  	return newWatcher(filepath.Join(h.dir, name), decoder, &Options{WaitDuration: 1 * time.Millisecond})
    56  }
    57  
    58  func (h *harness) CreateVariable(ctx context.Context, name string, val []byte) error {
    59  	// Write to a temporary file and rename; otherwise,
    60  	// Watch can read an empty file during the write.
    61  	tmp, err := ioutil.TempFile(h.dir, "tmp")
    62  	if err != nil {
    63  		return err
    64  	}
    65  	if _, err := tmp.Write(val); err != nil {
    66  		tmp.Close()
    67  		return err
    68  	}
    69  	tmp.Close()
    70  	return os.Rename(tmp.Name(), filepath.Join(h.dir, name))
    71  }
    72  
    73  func (h *harness) UpdateVariable(ctx context.Context, name string, val []byte) error {
    74  	return h.CreateVariable(ctx, name, val)
    75  }
    76  
    77  func (h *harness) DeleteVariable(ctx context.Context, name string) error {
    78  	path := filepath.Join(h.dir, name)
    79  	return os.Remove(path)
    80  }
    81  
    82  func (h *harness) Close() {
    83  	h.closer()
    84  }
    85  
    86  func (h *harness) Mutable() bool { return true }
    87  
    88  func TestConformance(t *testing.T) {
    89  	drivertest.RunConformanceTests(t, newHarness, []drivertest.AsTest{verifyAs{}})
    90  }
    91  
    92  type verifyAs struct{}
    93  
    94  func (verifyAs) Name() string {
    95  	return "verify As"
    96  }
    97  
    98  func (verifyAs) SnapshotCheck(s *runtimevar.Snapshot) error {
    99  	var ss string
   100  	if s.As(&ss) {
   101  		return errors.New("Snapshot.As expected to fail")
   102  	}
   103  	return nil
   104  }
   105  
   106  func (verifyAs) ErrorCheck(v *runtimevar.Variable, err error) error {
   107  	var ss string
   108  	if v.ErrorAs(err, &ss) {
   109  		return errors.New("runtimevar.ErrorAs expected to fail")
   110  	}
   111  	return nil
   112  }
   113  
   114  // Filevar-specific tests.
   115  
   116  func TestOpenVariable(t *testing.T) {
   117  	dir, err := ioutil.TempDir("", "filevar_test-")
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  
   122  	tests := []struct {
   123  		description string
   124  		path        string
   125  		decoder     *runtimevar.Decoder
   126  		want        string
   127  		wantErr     bool
   128  	}{
   129  		{
   130  			description: "empty path results in error",
   131  			decoder:     runtimevar.StringDecoder,
   132  			wantErr:     true,
   133  		},
   134  		{
   135  			description: "empty decoder results in error",
   136  			path:        filepath.Join(dir, "foo.txt"),
   137  			wantErr:     true,
   138  		},
   139  		{
   140  			description: "basic path works",
   141  			path:        filepath.Join(dir, "foo.txt"),
   142  			decoder:     runtimevar.StringDecoder,
   143  			want:        filepath.Join(dir, "foo.txt"),
   144  		},
   145  		{
   146  			description: "path with extra relative dirs works and is cleaned up",
   147  			path:        filepath.Join(dir, "bar/../foo.txt"),
   148  			decoder:     runtimevar.StringDecoder,
   149  			want:        filepath.Join(dir, "foo.txt"),
   150  		},
   151  	}
   152  
   153  	for _, test := range tests {
   154  		t.Run(test.description, func(t *testing.T) {
   155  			// Create driver impl.
   156  			drv, err := newWatcher(test.path, test.decoder, nil)
   157  			if (err != nil) != test.wantErr {
   158  				t.Errorf("got err %v want error %v", err, test.wantErr)
   159  			}
   160  			if drv != nil {
   161  				if drv.path != test.want {
   162  					t.Errorf("got %q want %q", drv.path, test.want)
   163  				}
   164  				drv.Close()
   165  			}
   166  
   167  			// Create portable type.
   168  			w, err := OpenVariable(test.path, test.decoder, nil)
   169  			if (err != nil) != test.wantErr {
   170  				t.Errorf("got err %v want error %v", err, test.wantErr)
   171  			}
   172  			if w != nil {
   173  				w.Close()
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestOpenVariableURL(t *testing.T) {
   180  	dir, err := ioutil.TempDir("", "gcdk-filevar-example")
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	defer os.RemoveAll(dir)
   185  
   186  	jsonPath := filepath.Join(dir, "myvar.json")
   187  	if err := ioutil.WriteFile(jsonPath, []byte(`{"Foo": "Bar"}`), 0666); err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	txtPath := filepath.Join(dir, "myvar.txt")
   191  	if err := ioutil.WriteFile(txtPath, []byte("hello world!"), 0666); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	nonexistentPath := filepath.Join(dir, "filenotfound")
   195  	ctx := context.Background()
   196  	secretsPath := filepath.Join(dir, "mysecret.txt")
   197  	cleanup, err := setupTestSecrets(ctx, dir, secretsPath)
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  	defer cleanup()
   202  
   203  	// Convert paths to a URL path, adding a leading "/" if needed on Windows
   204  	// (on Unix, dirpath already has a leading "/").
   205  	jsonPath = filepath.ToSlash(jsonPath)
   206  	txtPath = filepath.ToSlash(txtPath)
   207  	nonexistentPath = filepath.ToSlash(nonexistentPath)
   208  	secretsPath = filepath.ToSlash(secretsPath)
   209  	if os.PathSeparator != '/' {
   210  		if !strings.HasPrefix(jsonPath, "/") {
   211  			jsonPath = "/" + jsonPath
   212  		}
   213  		if !strings.HasPrefix(txtPath, "/") {
   214  			txtPath = "/" + txtPath
   215  		}
   216  		if !strings.HasPrefix(nonexistentPath, "/") {
   217  			nonexistentPath = "/" + nonexistentPath
   218  		}
   219  		if !strings.HasPrefix(secretsPath, "/") {
   220  			secretsPath = "/" + secretsPath
   221  		}
   222  	}
   223  
   224  	tests := []struct {
   225  		URL          string
   226  		WantErr      bool
   227  		WantWatchErr bool
   228  		Want         interface{}
   229  	}{
   230  		// Variable construction succeeds, but the file does not exist.
   231  		{"file://" + nonexistentPath, false, true, nil},
   232  		// Variable construction fails due to invalid decoder arg.
   233  		{"file://" + txtPath + "?decoder=notadecoder", true, false, nil},
   234  		// Variable construction fails due to invalid arg.
   235  		{"file://" + txtPath + "?param=value", true, false, nil},
   236  		// Working example with default decoder.
   237  		{"file://" + txtPath, false, false, []byte("hello world!")},
   238  		// Working example with string decoder.
   239  		{"file://" + txtPath + "?decoder=string", false, false, "hello world!"},
   240  		// Working example with JSON decoder.
   241  		{"file://" + jsonPath + "?decoder=jsonmap", false, false, &map[string]interface{}{"Foo": "Bar"}},
   242  		// Working example with decrypt (default) decoder.
   243  		{"file://" + secretsPath + "?decoder=decrypt", false, false, []byte(`{"Foo":"Bar"}`)},
   244  		// Working example with decrypt+bytes decoder.
   245  		{"file://" + secretsPath + "?decoder=decrypt+bytes", false, false, []byte(`{"Foo":"Bar"}`)},
   246  		// Working example with decrypt+json decoder.
   247  		{"file://" + secretsPath + "?decoder=decrypt+jsonmap", false, false, &map[string]interface{}{"Foo": "Bar"}},
   248  		// Working example with escaped decrypt+json decoder
   249  		{"file://" + secretsPath + "?decoder=" + url.QueryEscape("decrypt+jsonmap"), false, false, &map[string]interface{}{"Foo": "Bar"}},
   250  	}
   251  
   252  	for _, test := range tests {
   253  		t.Run(test.URL, func(t *testing.T) {
   254  			v, err := runtimevar.OpenVariable(ctx, test.URL)
   255  			if (err != nil) != test.WantErr {
   256  				t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   257  			}
   258  			if err != nil {
   259  				return
   260  			}
   261  			defer v.Close()
   262  			snapshot, err := v.Watch(ctx)
   263  			if (err != nil) != test.WantWatchErr {
   264  				t.Errorf("%s: got Watch error %v, want error %v", test.URL, err, test.WantWatchErr)
   265  			}
   266  			if err != nil {
   267  				return
   268  			}
   269  			if !cmp.Equal(snapshot.Value, test.Want) {
   270  				t.Errorf("%s: got snapshot value\n%v\n  want\n%v", test.URL, snapshot.Value, test.Want)
   271  			}
   272  		})
   273  	}
   274  }
   275  
   276  func setupTestSecrets(ctx context.Context, dir, secretsPath string) (func(), error) {
   277  	const keeperEnv = "RUNTIMEVAR_KEEPER_URL"
   278  	const keeperURL = "base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4="
   279  	oldURL := os.Getenv(keeperEnv)
   280  	os.Setenv(keeperEnv, keeperURL)
   281  	cleanup := func() { os.Setenv(keeperEnv, oldURL) }
   282  
   283  	k, err := secrets.OpenKeeper(ctx, keeperURL)
   284  	if err != nil {
   285  		return cleanup, err
   286  	}
   287  	sc, err := k.Encrypt(ctx, []byte(`{"Foo":"Bar"}`))
   288  	if err != nil {
   289  		return cleanup, err
   290  	}
   291  	if err := ioutil.WriteFile(secretsPath, sc, 0666); err != nil {
   292  		return cleanup, err
   293  	}
   294  	return cleanup, nil
   295  }