]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blobdiff - main_test.go
nsscash: main_test: refactor in preparation for HTTPS
[nsscash/nsscash.git] / main_test.go
index 21dc0463958659df5151cf34edf29c82c26634ce..ab5e9b7111399a5d999d1b794712d8e05a31e4c0 100644 (file)
@@ -184,10 +184,35 @@ func TestMainFetch(t *testing.T) {
        defer log.SetOutput(os.Stderr)
 
        tests := []func(args){
+               // Perform most tests with passwd for simplicity
+               fetchPasswdCacheFileDoesNotExist,
+               fetchPasswd404,
+               fetchPasswdEmpty,
+               fetchPasswdInvalid,
+               fetchPasswdLimits,
+               fetchPasswd,
+               // Tests for plain and group
+               fetchPlainEmpty,
+               fetchPlain,
+               fetchGroupEmpty,
+               fetchGroupInvalid,
+               fetchGroupLimits,
+               fetchGroup,
                // Special tests
                fetchNoConfig,
+               fetchStateCannotRead,
+               fetchStateInvalid,
+               fetchStateCannotWrite,
+               fetchCannotDeploy,
+               fetchSecondFetchFails,
        }
 
+       for _, f := range tests {
+               runMainTest(t, f)
+       }
+}
+
+func runMainTest(t *testing.T, f func(args)) {
        cleanup := []string{
                configPath,
                statePath,
@@ -196,7 +221,6 @@ func TestMainFetch(t *testing.T) {
                groupPath,
        }
 
-       for _, f := range tests {
                // NOTE: This is not guaranteed to work according to reflect's
                // documentation but seems to work reliable for normal
                // functions.
@@ -217,9 +241,10 @@ func TestMainFetch(t *testing.T) {
                        }
 
                        var handler func(http.ResponseWriter, *http.Request)
-                       ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-                               handler(w, r)
-                       }))
+                       ts := httptest.NewServer(http.HandlerFunc(
+                               func(w http.ResponseWriter, r *http.Request) {
+                                       handler(w, r)
+                               }))
                        defer ts.Close()
 
                        f(args{
@@ -228,7 +253,355 @@ func TestMainFetch(t *testing.T) {
                                handler: &handler,
                        })
                })
+}
+
+func fetchPasswdCacheFileDoesNotExist(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "file.path \""+passwdPath+"\" must exist")
+
+       mustNotExist(t, statePath, passwdPath, plainPath, groupPath)
+}
+
+func fetchPasswd404(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+       mustCreate(t, passwdPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               // 404
+               w.WriteHeader(http.StatusNotFound)
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "status code 404")
+
+       mustNotExist(t, statePath, plainPath, groupPath)
+       mustBeOld(a.t, passwdPath)
+}
+
+func fetchPasswdEmpty(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+       mustCreate(t, passwdPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               // Empty response
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "refusing to use empty passwd file")
+
+       mustNotExist(t, statePath, plainPath, groupPath)
+       mustBeOld(t, passwdPath)
+}
+
+func fetchPasswdInvalid(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+       mustCreate(t, passwdPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/passwd" {
+                       return
+               }
+
+               fmt.Fprintln(w, "root:x:invalid:0:root:/root:/bin/bash")
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "invalid uid in line")
+
+       mustNotExist(t, statePath, plainPath, groupPath)
+       mustBeOld(t, passwdPath)
+}
+
+func fetchPasswdLimits(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+       mustCreate(t, passwdPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/passwd" {
+                       return
+               }
+
+               fmt.Fprint(w, "root:x:0:0:root:/root:/bin/bash")
+               for i := 0; i < 65536; i++ {
+                       fmt.Fprint(w, "x")
+               }
+               fmt.Fprint(w, "\n")
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "passwd too large to serialize")
+
+       mustNotExist(t, statePath, plainPath, groupPath)
+       mustBeOld(t, passwdPath)
+}
+
+func fetchPasswd(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+       mustCreate(t, passwdPath)
+       mustHaveHash(t, passwdPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       t.Log("First fetch, write files")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/passwd" {
+                       return
+               }
+
+               // No "Last-Modified" header
+               fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+               fmt.Fprintln(w, "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin")
        }
+
+       err := mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, plainPath, groupPath)
+       mustBeNew(t, passwdPath, statePath)
+       // The actual content of passwdPath is verified by the NSS tests
+       mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+       t.Log("Fetch again, no support for Last-Modified")
+
+       mustMakeOld(t, passwdPath, statePath)
+
+       err = mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, plainPath, groupPath)
+       mustBeNew(t, passwdPath, statePath)
+       mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+       t.Log("Fetch again, support for Last-Modified, but not retrieved yet")
+
+       mustMakeOld(t, passwdPath, statePath)
+
+       lastChange := time.Now()
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/passwd" {
+                       return
+               }
+
+               modified := r.Header.Get("If-Modified-Since")
+               if modified != "" {
+                       x, err := http.ParseTime(modified)
+                       if err != nil {
+                               t.Fatalf("invalid If-Modified-Since %v",
+                                       modified)
+                       }
+                       if !x.Before(lastChange) {
+                               w.WriteHeader(http.StatusNotModified)
+                               return
+                       }
+               }
+
+               w.Header().Add("Last-Modified",
+                       lastChange.Format(http.TimeFormat))
+               fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+               fmt.Fprintln(w, "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin")
+       }
+
+       err = mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, plainPath, groupPath)
+       mustBeNew(t, passwdPath, statePath)
+       mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+       t.Log("Fetch again, support for Last-Modified")
+
+       mustMakeOld(t, passwdPath, statePath)
+
+       err = mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, plainPath, groupPath)
+       mustBeOld(t, passwdPath)
+       mustBeNew(t, statePath)
+       mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+       t.Log("Corrupt local passwd cache, fetched again")
+
+       os.Chmod(passwdPath, 0644) // make writable again
+       mustCreate(t, passwdPath)
+       mustMakeOld(t, passwdPath, statePath)
+
+       err = mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, plainPath, groupPath)
+       mustBeNew(t, passwdPath, statePath)
+       mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+}
+
+func fetchPlainEmpty(a args) {
+       t := a.t
+       mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "plain"
+url = "%[2]s/plain"
+path = "%[3]s"
+`, statePath, a.url, plainPath))
+       mustCreate(t, plainPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               // Empty response
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "refusing to use empty response")
+
+       mustNotExist(t, statePath, passwdPath, groupPath)
+       mustBeOld(t, plainPath)
+}
+
+func fetchPlain(a args) {
+       t := a.t
+       mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "plain"
+url = "%[2]s/plain"
+path = "%[3]s"
+`, statePath, a.url, plainPath))
+       mustCreate(t, plainPath)
+       mustHaveHash(t, plainPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/plain" {
+                       return
+               }
+
+               fmt.Fprintln(w, "some file")
+       }
+
+       err := mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, passwdPath, groupPath)
+       mustBeNew(t, plainPath, statePath)
+       mustHaveHash(t, plainPath, "0e08b5e8c10abc3e455b75286ba4a1fbd56e18a5")
+
+       // Remaining functionality already tested in fetchPasswd()
+}
+
+func fetchGroupEmpty(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               // Empty response
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "refusing to use empty group file")
+
+       mustNotExist(t, statePath, passwdPath, plainPath)
+       mustBeOld(t, groupPath)
+}
+
+func fetchGroupInvalid(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/group" {
+                       return
+               }
+
+               fmt.Fprintln(w, "root:x::")
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "invalid gid in line")
+
+       mustNotExist(t, statePath, passwdPath, plainPath)
+       mustBeOld(t, groupPath)
+}
+
+func fetchGroupLimits(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/group" {
+                       return
+               }
+
+               fmt.Fprint(w, "root:x:0:")
+               for i := 0; i < 65536; i++ {
+                       fmt.Fprint(w, "x")
+               }
+               fmt.Fprint(w, "\n")
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "group too large to serialize")
+
+       mustNotExist(t, statePath, passwdPath, plainPath)
+       mustBeOld(t, groupPath)
+}
+
+func fetchGroup(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+       mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/group" {
+                       return
+               }
+
+               fmt.Fprintln(w, "root:x:0:")
+               fmt.Fprintln(w, "daemon:x:1:andariel,duriel,mephisto,diablo,baal")
+       }
+
+       err := mainFetch(configPath)
+       if err != nil {
+               t.Error(err)
+       }
+
+       mustNotExist(t, passwdPath, plainPath)
+       mustBeNew(t, groupPath, statePath)
+       // The actual content of groupPath is verified by the NSS tests
+       mustHaveHash(t, groupPath, "8c27a8403278ba2e392b86d98d4dff1fdefcafdd")
+
+       // Remaining functionality already tested in fetchPasswd()
 }
 
 func fetchNoConfig(a args) {
@@ -240,3 +613,127 @@ func fetchNoConfig(a args) {
 
        mustNotExist(t, configPath, statePath, passwdPath, plainPath, groupPath)
 }
+
+func fetchStateCannotRead(a args) {
+       t := a.t
+       mustWritePasswdConfig(t, a.url)
+
+       mustCreate(t, statePath)
+       err := os.Chmod(statePath, 0000)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       err = mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               statePath+": permission denied")
+
+       mustNotExist(t, passwdPath, plainPath, groupPath)
+}
+
+func fetchStateInvalid(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, statePath)
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "unexpected end of JSON input")
+
+       mustNotExist(t, groupPath, passwdPath, plainPath)
+       mustBeOld(t, statePath)
+}
+
+func fetchStateCannotWrite(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+       mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               // To prevent mainFetch() from trying to update groupPath
+               // which will also fail
+               w.WriteHeader(http.StatusNotModified)
+       }
+
+       err := os.Chmod("testdata", 0500)
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.Chmod("testdata", 0755)
+
+       err = mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "permission denied")
+
+       mustNotExist(t, statePath, passwdPath, plainPath)
+       mustBeOld(t, groupPath)
+}
+
+func fetchCannotDeploy(a args) {
+       t := a.t
+       mustWriteGroupConfig(t, a.url)
+       mustCreate(t, groupPath)
+       mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != "/group" {
+                       return
+               }
+
+               fmt.Fprintln(w, "root:x:0:")
+               fmt.Fprintln(w, "daemon:x:1:andariel,duriel,mephisto,diablo,baal")
+       }
+
+       err := os.Chmod("testdata", 0500)
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.Chmod("testdata", 0755)
+
+       err = mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "permission denied")
+
+       mustNotExist(t, statePath, passwdPath, plainPath)
+       mustBeOld(t, groupPath)
+}
+
+func fetchSecondFetchFails(a args) {
+       t := a.t
+       mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "passwd"
+url = "%[2]s/passwd"
+path = "%[3]s"
+
+[[file]]
+type = "group"
+url = "%[2]s/group"
+path = "%[4]s"
+`, statePath, a.url, passwdPath, groupPath))
+       mustCreate(t, passwdPath)
+       mustCreate(t, groupPath)
+       mustHaveHash(t, passwdPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+       mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+       *a.handler = func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path == "/passwd" {
+                       fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+               }
+               if r.URL.Path == "/group" {
+                       w.WriteHeader(http.StatusNotFound)
+               }
+       }
+
+       err := mainFetch(configPath)
+       mustBeErrorWithSubstring(t, err,
+               "status code 404")
+
+       mustNotExist(t, statePath, plainPath)
+       // Even though passwd was successfully fetched, no files were modified
+       // because the second fetch failed
+       mustBeOld(t, passwdPath, groupPath)
+}