mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 13:21:08 +00:00 
			
		
		
		
	 821a08a6e3
			
		
	
	
		821a08a6e3
		
			
		
	
	
	
	
		
			
			* httpcaddyfile: Fix `protocols` global option parsing When checking for a block, the current nesting must be used, otherwise it returns the wrong thing. * Adjust adapt test to cover the broken behaviour that is now fixed * Fix some admin tests which suddenly run even with -short
		
			
				
	
	
		
			199 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddy
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"reflect"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| )
 | |
| 
 | |
| var testCfg = []byte(`{
 | |
| 			"apps": {
 | |
| 				"http": {
 | |
| 					"servers": {
 | |
| 						"myserver": {
 | |
| 							"listen": ["tcp/localhost:8080-8084"],
 | |
| 							"read_timeout": "30s"
 | |
| 						},
 | |
| 						"yourserver": {
 | |
| 							"listen": ["127.0.0.1:5000"],
 | |
| 							"read_header_timeout": "15s"
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		`)
 | |
| 
 | |
| func TestUnsyncedConfigAccess(t *testing.T) {
 | |
| 	// each test is performed in sequence, so
 | |
| 	// each change builds on the previous ones;
 | |
| 	// the config is not reset between tests
 | |
| 	for i, tc := range []struct {
 | |
| 		method    string
 | |
| 		path      string // rawConfigKey will be prepended
 | |
| 		payload   string
 | |
| 		expect    string // JSON representation of what the whole config is expected to be after the request
 | |
| 		shouldErr bool
 | |
| 	}{
 | |
| 		{
 | |
| 			method:  "POST",
 | |
| 			path:    "",
 | |
| 			payload: `{"foo": "bar", "list": ["a", "b", "c"]}`, // starting value
 | |
| 			expect:  `{"foo": "bar", "list": ["a", "b", "c"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "POST",
 | |
| 			path:    "/foo",
 | |
| 			payload: `"jet"`,
 | |
| 			expect:  `{"foo": "jet", "list": ["a", "b", "c"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "POST",
 | |
| 			path:    "/bar",
 | |
| 			payload: `{"aa": "bb", "qq": "zz"}`,
 | |
| 			expect:  `{"foo": "jet", "bar": {"aa": "bb", "qq": "zz"}, "list": ["a", "b", "c"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method: "DELETE",
 | |
| 			path:   "/bar/qq",
 | |
| 			expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "POST",
 | |
| 			path:    "/list",
 | |
| 			payload: `"e"`,
 | |
| 			expect:  `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "PUT",
 | |
| 			path:    "/list/3",
 | |
| 			payload: `"d"`,
 | |
| 			expect:  `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method: "DELETE",
 | |
| 			path:   "/list/3",
 | |
| 			expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "PATCH",
 | |
| 			path:    "/list/3",
 | |
| 			payload: `"d"`,
 | |
| 			expect:  `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d"]}`,
 | |
| 		},
 | |
| 		{
 | |
| 			method:  "POST",
 | |
| 			path:    "/list/...",
 | |
| 			payload: `["e", "f", "g"]`,
 | |
| 			expect:  `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e", "f", "g"]}`,
 | |
| 		},
 | |
| 	} {
 | |
| 		err := unsyncedConfigAccess(tc.method, rawConfigKey+tc.path, []byte(tc.payload), nil)
 | |
| 
 | |
| 		if tc.shouldErr && err == nil {
 | |
| 			t.Fatalf("Test %d: Expected error return value, but got: %v", i, err)
 | |
| 		}
 | |
| 		if !tc.shouldErr && err != nil {
 | |
| 			t.Fatalf("Test %d: Should not have had error return value, but got: %v", i, err)
 | |
| 		}
 | |
| 
 | |
| 		// decode the expected config so we can do a convenient DeepEqual
 | |
| 		var expectedDecoded any
 | |
| 		err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
 | |
| 		}
 | |
| 
 | |
| 		// make sure the resulting config is as we expect it
 | |
| 		if !reflect.DeepEqual(rawCfg[rawConfigKey], expectedDecoded) {
 | |
| 			t.Fatalf("Test %d:\nExpected:\n\t%#v\nActual:\n\t%#v",
 | |
| 				i, expectedDecoded, rawCfg[rawConfigKey])
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestLoadConcurrent exercises Load under concurrent conditions
 | |
| // and is most useful under test with `-race` enabled.
 | |
| func TestLoadConcurrent(t *testing.T) {
 | |
| 	var wg sync.WaitGroup
 | |
| 
 | |
| 	for i := 0; i < 100; i++ {
 | |
| 		wg.Add(1)
 | |
| 		go func() {
 | |
| 			_ = Load(testCfg, true)
 | |
| 			wg.Done()
 | |
| 		}()
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| }
 | |
| 
 | |
| type fooModule struct {
 | |
| 	IntField int
 | |
| 	StrField string
 | |
| }
 | |
| 
 | |
| func (fooModule) CaddyModule() ModuleInfo {
 | |
| 	return ModuleInfo{
 | |
| 		ID:  "foo",
 | |
| 		New: func() Module { return new(fooModule) },
 | |
| 	}
 | |
| }
 | |
| func (fooModule) Start() error { return nil }
 | |
| func (fooModule) Stop() error  { return nil }
 | |
| 
 | |
| func TestETags(t *testing.T) {
 | |
| 	RegisterModule(fooModule{})
 | |
| 
 | |
| 	if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
 | |
| 		t.Fatalf("loading: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	const key = "/" + rawConfigKey + "/apps/foo"
 | |
| 
 | |
| 	// try update the config with the wrong etag
 | |
| 	err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
 | |
| 	if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
 | |
| 		t.Fatalf("expected precondition failed; got %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// get the etag
 | |
| 	hash := etagHasher()
 | |
| 	if err := readConfig(key, hash); err != nil {
 | |
| 		t.Fatalf("reading: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	// do the same update with the correct key
 | |
| 	err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("expected update to work; got %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// now try another update. The hash should no longer match and we should get precondition failed
 | |
| 	err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
 | |
| 	if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
 | |
| 		t.Fatalf("expected precondition failed; got %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func BenchmarkLoad(b *testing.B) {
 | |
| 	for i := 0; i < b.N; i++ {
 | |
| 		Load(testCfg, true)
 | |
| 	}
 | |
| }
 |