Compare commits

..

6 Commits

31 changed files with 1285 additions and 2536 deletions

View File

@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go: ["1.17", "1.16"] go: ["1.23", "1.22", "1.21"]
name: Go ${{ matrix.go }} - Build name: Go ${{ matrix.go }} - Build
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go: ["1.17", "1.16"] go: ["1.23", "1.22", "1.21"]
name: Go ${{ matrix.go }} - Test name: Go ${{ matrix.go }} - Test
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -101,7 +101,7 @@ Runs an HTTP server with (MITM) proxy, GraphQL service, and a web based admin in
Options: Options:
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem") --cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem") --key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
--db Database directory path. (Default: "~/.hetty/db") --db Database file path. Creates file if it doesn't exist. (Default: "~/.hetty/hetty.db")
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080") --addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false) --chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
--verbose Enable verbose logging. --verbose Enable verbose logging.
@ -146,14 +146,10 @@ Guidelines](CONTRIBUTING.md) for details.
## Sponsors ## Sponsors
<p><a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=hetty">
<img src="https://hetty.xyz/img/tines-sponsorship-badge.png" width="140" alt="Sponsored by Tines">
</a></p>
💖 Are you enjoying Hetty? You can [sponsor me](https://github.com/sponsors/dstotijn)! 💖 Are you enjoying Hetty? You can [sponsor me](https://github.com/sponsors/dstotijn)!
## License ## License
[MIT](LICENSE) [MIT](LICENSE)
© 2022 Hetty Software © 20192025 Hetty Software

View File

@ -16,16 +16,15 @@ import (
"strings" "strings"
"github.com/chromedp/chromedp" "github.com/chromedp/chromedp"
badgerdb "github.com/dgraph-io/badger/v3"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"go.etcd.io/bbolt"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/dstotijn/hetty/pkg/api" "github.com/dstotijn/hetty/pkg/api"
"github.com/dstotijn/hetty/pkg/chrome" "github.com/dstotijn/hetty/pkg/chrome"
"github.com/dstotijn/hetty/pkg/db/badger" "github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/proj" "github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/proxy/intercept" "github.com/dstotijn/hetty/pkg/proxy/intercept"
@ -51,7 +50,7 @@ Runs an HTTP server with (MITM) proxy, GraphQL service, and a web based admin in
Options: Options:
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem") --cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem") --key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
--db Database directory path. (Default: "~/.hetty/db") --db Database file path. Creates file if it doesn't exist. (Default: "~/.hetty/hetty.db")
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080") --addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false) --chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
--verbose Enable verbose logging. --verbose Enable verbose logging.
@ -89,7 +88,7 @@ func NewHettyCommand() (*ffcli.Command, *Config) {
"Path to root CA certificate. Creates a new certificate if file doesn't exist.") "Path to root CA certificate. Creates a new certificate if file doesn't exist.")
fs.StringVar(&cmd.key, "key", "~/.hetty/hetty_key.pem", fs.StringVar(&cmd.key, "key", "~/.hetty/hetty_key.pem",
"Path to root CA private key. Creates a new private key if file doesn't exist.") "Path to root CA private key. Creates a new private key if file doesn't exist.")
fs.StringVar(&cmd.db, "db", "~/.hetty/db", "Database directory path.") fs.StringVar(&cmd.db, "db", "~/.hetty/hetty.db", "Database file path. Creates file if it doesn't exist.")
fs.StringVar(&cmd.addr, "addr", ":8080", "TCP address to listen on, in the form \"host:port\".") fs.StringVar(&cmd.addr, "addr", ":8080", "TCP address to listen on, in the form \"host:port\".")
fs.BoolVar(&cmd.chrome, "chrome", false, "Launch Chrome with proxy settings applied and certificate errors ignored.") fs.BoolVar(&cmd.chrome, "chrome", false, "Launch Chrome with proxy settings applied and certificate errors ignored.")
fs.BoolVar(&cmd.version, "version", false, "Output version.") fs.BoolVar(&cmd.version, "version", false, "Output version.")
@ -154,25 +153,21 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
cmd.config.logger.Fatal("Failed to load or create CA key pair.", zap.Error(err)) cmd.config.logger.Fatal("Failed to load or create CA key pair.", zap.Error(err))
} }
// BadgerDB logs some verbose entries with `INFO` level, so unless dbLogger := cmd.config.logger.Named("boltdb").Sugar()
// we're running in debug mode, bump the minimal level to `WARN`. boltOpts := *bbolt.DefaultOptions
dbLogger := cmd.config.logger.Named("badgerdb").WithOptions(zap.IncreaseLevel(zapcore.WarnLevel)) boltOpts.Logger = &bolt.Logger{SugaredLogger: dbLogger}
dbSugaredLogger := dbLogger.Sugar() boltDB, err := bolt.OpenDatabase(dbPath, &boltOpts)
badger, err := badger.OpenDatabase(
badgerdb.DefaultOptions(dbPath).WithLogger(badger.NewLogger(dbSugaredLogger)),
)
if err != nil { if err != nil {
cmd.config.logger.Fatal("Failed to open database.", zap.Error(err)) cmd.config.logger.Fatal("Failed to open database.", zap.Error(err))
} }
defer badger.Close() defer boltDB.Close()
scope := &scope.Scope{} scope := &scope.Scope{}
reqLogService := reqlog.NewService(reqlog.Config{ reqLogService := reqlog.NewService(reqlog.Config{
Scope: scope, Scope: scope,
Repository: badger, Repository: boltDB,
Logger: cmd.config.logger.Named("reqlog").Sugar(), Logger: cmd.config.logger.Named("reqlog").Sugar(),
}) })
@ -181,12 +176,12 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
}) })
senderService := sender.NewService(sender.Config{ senderService := sender.NewService(sender.Config{
Repository: badger, Repository: boltDB,
ReqLogService: reqLogService, ReqLogService: reqLogService,
}) })
projService, err := proj.NewService(proj.Config{ projService, err := proj.NewService(proj.Config{
Repository: badger, Repository: boltDB,
InterceptService: interceptService, InterceptService: interceptService,
ReqLogService: reqLogService, ReqLogService: reqLogService,
SenderService: senderService, SenderService: senderService,
@ -221,12 +216,17 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
host, _, _ := net.SplitHostPort(req.Host) host, _, _ := net.SplitHostPort(req.Host)
// Serve local admin routes when the `Host` is well-known, e.g. `[hostname]:[port]`, // Serve local admin routes when either:
// `hetty.proxy`, `localhost:[port]` or the listen addr `[host]:[port]`. // - The `Host` is well-known, e.g. `hetty.proxy`, `localhost:[port]`
// or the listen addr `[host]:[port]`.
// - The request is not for TLS proxying (e.g. no `CONNECT`) and not
// for proxying an external URL. E.g. Request-Line (RFC 7230, Section 3.1.1)
// has no scheme.
return strings.EqualFold(host, hostname) || return strings.EqualFold(host, hostname) ||
req.Host == "hetty.proxy" || req.Host == "hetty.proxy" ||
req.Host == fmt.Sprintf("%v:%v", "localhost", listenPort) || req.Host == fmt.Sprintf("%v:%v", "localhost", listenPort) ||
req.Host == fmt.Sprintf("%v:%v", listenHost, listenPort) req.Host == fmt.Sprintf("%v:%v", listenHost, listenPort) ||
req.Method != http.MethodConnect && !strings.HasPrefix(req.RequestURI, "http://")
}).Subrouter().StrictSlash(true) }).Subrouter().StrictSlash(true)
// GraphQL server. // GraphQL server.

23
go.mod
View File

@ -1,57 +1,46 @@
module github.com/dstotijn/hetty module github.com/dstotijn/hetty
go 1.17 go 1.23
toolchain go1.23.4
require ( require (
github.com/99designs/gqlgen v0.14.0 github.com/99designs/gqlgen v0.14.0
github.com/chromedp/chromedp v0.7.8 github.com/chromedp/chromedp v0.7.8
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/google/go-cmp v0.5.6 github.com/google/go-cmp v0.5.6
github.com/gorilla/mux v1.7.4 github.com/gorilla/mux v1.7.4
github.com/matryer/moq v0.2.5
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/peterbourgon/ff/v3 v3.1.2 github.com/peterbourgon/ff/v3 v3.1.2
github.com/smallstep/truststore v0.11.0 github.com/smallstep/truststore v0.11.0
github.com/vektah/gqlparser/v2 v2.2.0 github.com/vektah/gqlparser/v2 v2.2.0
go.etcd.io/bbolt v1.4.0-beta.0
go.uber.org/zap v1.21.0 go.uber.org/zap v1.21.0
) )
require ( require (
github.com/agnivade/levenshtein v1.1.0 // indirect github.com/agnivade/levenshtein v1.1.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf // indirect github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf // indirect
github.com/chromedp/sysutil v1.0.0 // indirect github.com/chromedp/sysutil v1.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0 // indirect github.com/gobwas/ws v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.12.3 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/matryer/moq v0.2.5 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/urfave/cli/v2 v2.1.1 // indirect github.com/urfave/cli/v2 v2.1.1 // indirect
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect
go.opencensus.io v0.22.5 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.4.2 // indirect golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/tools v0.1.5 // indirect golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect

99
go.sum
View File

@ -1,9 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI= github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE= github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM= github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
@ -11,63 +8,28 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf h1:1omDWNUsWxn2HpiMiMuyRmzjl9uG7RP3IE6GTlpgJWU= github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf h1:1omDWNUsWxn2HpiMiMuyRmzjl9uG7RP3IE6GTlpgJWU=
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
github.com/chromedp/chromedp v0.7.8 h1:JFPIFb28LPjcx6l6mUUzLOTD/TgswcTtg7KrDn8S/2I= github.com/chromedp/chromedp v0.7.8 h1:JFPIFb28LPjcx6l6mUUzLOTD/TgswcTtg7KrDn8S/2I=
github.com/chromedp/chromedp v0.7.8/go.mod h1:HcIUFBa5vA+u2QI3+xljiU59llUQ8lgGoLzYSCBfmUA= github.com/chromedp/chromedp v0.7.8/go.mod h1:HcIUFBa5vA+u2QI3+xljiU59llUQ8lgGoLzYSCBfmUA=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8=
github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@ -79,22 +41,17 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@ -114,7 +71,6 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM= github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE= github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
@ -124,8 +80,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@ -136,36 +90,25 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/smallstep/truststore v0.11.0 h1:JUTkQ4oHr40jHTS/A2t0usEhteMWG+45CDD2iJA/dIk= github.com/smallstep/truststore v0.11.0 h1:JUTkQ4oHr40jHTS/A2t0usEhteMWG+45CDD2iJA/dIk=
github.com/smallstep/truststore v0.11.0/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM= github.com/smallstep/truststore v0.11.0/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM= github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4= github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.etcd.io/bbolt v1.4.0-beta.0 h1:U7Y9yH6ZojEo5/BDFMXDXD1RNx9L7iKxudzqR68jLaM=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.etcd.io/bbolt v1.4.0-beta.0/go.mod h1:Qv5yHB6jkQESXT/uVfxJgUPMqgAyhL0GLxcQaz9bSec=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
@ -174,62 +117,46 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -243,12 +170,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@ -259,9 +180,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@ -40,10 +40,10 @@ var revHTTPProtocolMap = map[HTTPProtocol]string{
} }
type Resolver struct { type Resolver struct {
ProjectService proj.Service ProjectService *proj.Service
RequestLogService reqlog.Service RequestLogService *reqlog.Service
InterceptService *intercept.Service InterceptService *intercept.Service
SenderService sender.Service SenderService *sender.Service
} }
type ( type (
@ -865,7 +865,7 @@ func parseInterceptItem(item intercept.Item) (req HTTPRequest, err error) {
return req, nil return req, nil
} }
func parseProject(projSvc proj.Service, p proj.Project) Project { func parseProject(projSvc *proj.Service, p proj.Project) Project {
project := Project{ project := Project{
ID: p.ID, ID: p.ID,
Name: p.Name, Name: p.Name,

View File

@ -1,57 +0,0 @@
package badger
import (
"fmt"
"github.com/dgraph-io/badger/v3"
)
const (
// Key prefixes. Each prefix value should be unique.
projectPrefix = 0x00
reqLogPrefix = 0x01
resLogPrefix = 0x02
senderReqPrefix = 0x03
// Request log indices.
reqLogProjectIDIndex = 0x00
// Sender request indices.
senderReqProjectIDIndex = 0x00
)
// Database is used to store and retrieve data from an underlying Badger database.
type Database struct {
badger *badger.DB
}
// OpenDatabase opens a new Badger database.
func OpenDatabase(opts badger.Options) (*Database, error) {
db, err := badger.Open(opts)
if err != nil {
return nil, fmt.Errorf("badger: failed to open database: %w", err)
}
return &Database{badger: db}, nil
}
// Close closes the underlying Badger database.
func (db *Database) Close() error {
return db.badger.Close()
}
// DatabaseFromBadgerDB returns a Database with `db` set as the underlying
// Badger database.
func DatabaseFromBadgerDB(db *badger.DB) *Database {
return &Database{badger: db}
}
func entryKey(prefix, index byte, value []byte) []byte {
// Key consists of: | prefix (byte) | index (byte) | value
key := make([]byte, 2+len(value))
key[0] = prefix
key[1] = index
copy(key[2:len(value)+2], value)
return key
}

View File

@ -1,21 +0,0 @@
package badger
import (
"github.com/dgraph-io/badger/v3"
"go.uber.org/zap"
)
// Interface guard.
var _ badger.Logger = (*Logger)(nil)
type Logger struct {
*zap.SugaredLogger
}
func NewLogger(l *zap.SugaredLogger) *Logger {
return &Logger{l}
}
func (l *Logger) Warningf(template string, args ...interface{}) {
l.Warnf(template, args)
}

View File

@ -1,115 +0,0 @@
package badger
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/dgraph-io/badger/v3"
"github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/proj"
)
func (db *Database) UpsertProject(ctx context.Context, project proj.Project) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(project)
if err != nil {
return fmt.Errorf("badger: failed to encode project: %w", err)
}
err = db.badger.Update(func(txn *badger.Txn) error {
return txn.Set(entryKey(projectPrefix, 0, project.ID[:]), buf.Bytes())
})
if err != nil {
return fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) FindProjectByID(ctx context.Context, projectID ulid.ULID) (project proj.Project, err error) {
err = db.badger.View(func(txn *badger.Txn) error {
item, err := txn.Get(entryKey(projectPrefix, 0, projectID[:]))
if err != nil {
return err
}
err = item.Value(func(rawProject []byte) error {
return gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
})
if err != nil {
return fmt.Errorf("failed to retrieve or parse project: %w", err)
}
return nil
})
if errors.Is(err, badger.ErrKeyNotFound) {
return proj.Project{}, proj.ErrProjectNotFound
}
if err != nil {
return proj.Project{}, fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return project, nil
}
func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
err := db.ClearRequestLogs(ctx, projectID)
if err != nil {
return fmt.Errorf("badger: failed to delete project request logs: %w", err)
}
err = db.DeleteSenderRequests(ctx, projectID)
if err != nil {
return fmt.Errorf("badger: failed to delete project sender requests: %w", err)
}
err = db.badger.Update(func(txn *badger.Txn) error {
return txn.Delete(entryKey(projectPrefix, 0, projectID[:]))
})
if err != nil {
return fmt.Errorf("badger: failed to delete project item: %w", err)
}
return nil
}
func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
projects := make([]proj.Project, 0)
err := db.badger.View(func(txn *badger.Txn) error {
var rawProject []byte
prefix := entryKey(projectPrefix, 0, nil)
iterator := txn.NewIterator(badger.DefaultIteratorOptions)
defer iterator.Close()
for iterator.Seek(prefix); iterator.ValidForPrefix(prefix); iterator.Next() {
rawProject, err := iterator.Item().ValueCopy(rawProject)
if err != nil {
return fmt.Errorf("failed to copy value: %w", err)
}
var project proj.Project
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
if err != nil {
return fmt.Errorf("failed to decode project: %w", err)
}
projects = append(projects, project)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return projects, nil
}

View File

@ -1,328 +0,0 @@
package badger
import (
"bytes"
"context"
"encoding/gob"
"errors"
"math/rand"
"regexp"
"testing"
"time"
badgerdb "github.com/dgraph-io/badger/v3"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/scope"
)
//nolint:gosec
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
var regexpCompareOpt = cmp.Comparer(func(x, y *regexp.Regexp) bool {
switch {
case x == nil && y == nil:
return true
case x == nil || y == nil:
return false
default:
return x.String() == y.String()
}
})
func TestUpsertProject(t *testing.T) {
t.Parallel()
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
if err != nil {
t.Fatalf("failed to open badger database: %v", err)
}
database := DatabaseFromBadgerDB(badgerDB)
defer database.Close()
searchExpr, err := filter.ParseQuery("foo AND bar OR NOT baz")
if err != nil {
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
}
exp := proj.Project{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: "foobar",
Settings: proj.Settings{
ReqLogBypassOutOfScope: true,
ReqLogOnlyFindInScope: true,
ReqLogSearchExpr: searchExpr,
ScopeRules: []scope.Rule{
{
URL: regexp.MustCompile("^https://(.*)example.com(.*)$"),
Header: scope.Header{
Key: regexp.MustCompile("^X-Foo(.*)$"),
Value: regexp.MustCompile("^foo(.*)$"),
},
Body: regexp.MustCompile("^foo(.*)"),
},
},
},
}
err = database.UpsertProject(context.Background(), exp)
if err != nil {
t.Fatalf("unexpected error storing project: %v", err)
}
var rawProject []byte
err = badgerDB.View(func(txn *badgerdb.Txn) error {
item, err := txn.Get(entryKey(projectPrefix, 0, exp.ID[:]))
if err != nil {
return err
}
rawProject, err = item.ValueCopy(nil)
return err
})
if err != nil {
t.Fatalf("unexpected error retrieving project from database: %v", err)
}
got := proj.Project{}
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&got)
if err != nil {
t.Fatalf("unexpected error decoding project: %v", err)
}
if diff := cmp.Diff(exp, got, regexpCompareOpt, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
}
}
func TestFindProjectByID(t *testing.T) {
t.Parallel()
t.Run("existing project", func(t *testing.T) {
t.Parallel()
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
if err != nil {
t.Fatalf("failed to open badger database: %v", err)
}
database := DatabaseFromBadgerDB(badgerDB)
defer database.Close()
exp := proj.Project{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: "foobar",
Settings: proj.Settings{},
}
buf := bytes.Buffer{}
err = gob.NewEncoder(&buf).Encode(exp)
if err != nil {
t.Fatalf("unexpected error encoding project: %v", err)
}
err = badgerDB.Update(func(txn *badgerdb.Txn) error {
return txn.Set(entryKey(projectPrefix, 0, exp.ID[:]), buf.Bytes())
})
if err != nil {
t.Fatalf("unexpected error setting project: %v", err)
}
got, err := database.FindProjectByID(context.Background(), exp.ID)
if err != nil {
t.Fatalf("unexpected error finding project: %v", err)
}
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
}
})
t.Run("project not found", func(t *testing.T) {
t.Parallel()
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
if err != nil {
t.Fatalf("failed to open badger database: %v", err)
}
defer database.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
_, err = database.FindProjectByID(context.Background(), projectID)
if !errors.Is(err, proj.ErrProjectNotFound) {
t.Fatalf("expected `proj.ErrProjectNotFound`, got: %v", err)
}
})
}
func TestDeleteProject(t *testing.T) {
t.Parallel()
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
if err != nil {
t.Fatalf("failed to open badger database: %v", err)
}
database := DatabaseFromBadgerDB(badgerDB)
defer database.Close()
// Store fixtures.
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
senderReqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = badgerDB.Update(func(txn *badgerdb.Txn) error {
// Project item.
if err := txn.Set(entryKey(projectPrefix, 0, projectID[:]), nil); err != nil {
return err
}
// Sender request items.
if err := txn.Set(entryKey(senderReqPrefix, 0, senderReqID[:]), nil); err != nil {
return err
}
if err := txn.Set(entryKey(resLogPrefix, 0, senderReqID[:]), nil); err != nil {
return err
}
err := txn.Set(entryKey(senderReqPrefix, senderReqProjectIDIndex, append(projectID[:], senderReqID[:]...)), nil)
if err != nil {
return err
}
// Request log items.
if err := txn.Set(entryKey(reqLogPrefix, 0, reqLogID[:]), nil); err != nil {
return err
}
if err := txn.Set(entryKey(resLogPrefix, 0, reqLogID[:]), nil); err != nil {
return err
}
err = txn.Set(entryKey(reqLogPrefix, reqLogProjectIDIndex, append(projectID[:], reqLogID[:]...)), nil)
if err != nil {
return err
}
return nil
})
if err != nil {
t.Fatalf("unexpected error creating fixtures: %v", err)
}
err = database.DeleteProject(context.Background(), projectID)
if err != nil {
t.Fatalf("unexpected error deleting project: %v", err)
}
// Assert project key was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(projectPrefix, 0, projectID[:]))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert request log item was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(reqLogPrefix, 0, reqLogID[:]))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert response log item related to request log was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(resLogPrefix, 0, reqLogID[:]))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert request log project ID index key was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(reqLogPrefix, reqLogProjectIDIndex, append(projectID[:], reqLogID[:]...)))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert sender request item was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(senderReqPrefix, 0, senderReqID[:]))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert response log item related to sender request was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(resLogPrefix, 0, senderReqID[:]))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
// Assert sender request project ID index key was deleted.
err = badgerDB.View(func(txn *badgerdb.Txn) error {
_, err := txn.Get(entryKey(senderReqPrefix, senderReqProjectIDIndex, append(projectID[:], senderReqID[:]...)))
return err
})
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
}
}
func TestProjects(t *testing.T) {
t.Parallel()
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
if err != nil {
t.Fatalf("failed to open badger database: %v", err)
}
defer database.Close()
exp := []proj.Project{
{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: "one",
},
{
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
Name: "two",
},
}
// Store fixtures.
for _, project := range exp {
err = database.UpsertProject(context.Background(), project)
if err != nil {
t.Fatalf("unexpected error creating project fixture: %v", err)
}
}
got, err := database.Projects(context.Background())
if err != nil {
t.Fatalf("unexpected error finding projects: %v", err)
}
if len(exp) != len(got) {
t.Fatalf("expected %v projects, got: %v", len(exp), len(got))
}
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("projects not equal (-exp, +got):\n%v", diff)
}
}

View File

@ -1,260 +0,0 @@
package badger
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/dgraph-io/badger/v3"
"github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
)
func (db *Database) FindRequestLogs(
ctx context.Context,
filter reqlog.FindRequestsFilter,
scope *scope.Scope) ([]reqlog.RequestLog, error,
) {
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
return nil, reqlog.ErrProjectIDMustBeSet
}
txn := db.badger.NewTransaction(false)
defer txn.Discard()
reqLogIDs, err := findRequestLogIDsByProjectID(txn, filter.ProjectID)
if err != nil {
return nil, fmt.Errorf("badger: failed to find request log IDs: %w", err)
}
reqLogs := make([]reqlog.RequestLog, 0, len(reqLogIDs))
for _, reqLogID := range reqLogIDs {
reqLog, err := getRequestLogWithResponse(txn, reqLogID)
if err != nil {
return nil, fmt.Errorf("badger: failed to get request log (id: %v): %w", reqLogID.String(), err)
}
if filter.OnlyInScope {
if !reqLog.MatchScope(scope) {
continue
}
}
// Filter by search expression.
// TODO: Once pagination is introduced, this filter logic should be done
// as items are retrieved (e.g. when using a `badger.Iterator`).
if filter.SearchExpr != nil {
match, err := reqLog.Matches(filter.SearchExpr)
if err != nil {
return nil, fmt.Errorf(
"badger: failed to match search expression for request log (id: %v): %w",
reqLogID.String(), err,
)
}
if !match {
continue
}
}
reqLogs = append(reqLogs, reqLog)
}
return reqLogs, nil
}
func getRequestLogWithResponse(txn *badger.Txn, reqLogID ulid.ULID) (reqlog.RequestLog, error) {
item, err := txn.Get(entryKey(reqLogPrefix, 0, reqLogID[:]))
switch {
case errors.Is(err, badger.ErrKeyNotFound):
return reqlog.RequestLog{}, reqlog.ErrRequestNotFound
case err != nil:
return reqlog.RequestLog{}, fmt.Errorf("failed to lookup request log item: %w", err)
}
reqLog := reqlog.RequestLog{
ID: reqLogID,
}
err = item.Value(func(rawReqLog []byte) error {
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
if err != nil {
return fmt.Errorf("failed to decode request log: %w", err)
}
return nil
})
if err != nil {
return reqlog.RequestLog{}, fmt.Errorf("failed to retrieve or parse request log value: %w", err)
}
item, err = txn.Get(entryKey(resLogPrefix, 0, reqLogID[:]))
if errors.Is(err, badger.ErrKeyNotFound) {
return reqLog, nil
}
if err != nil {
return reqlog.RequestLog{}, fmt.Errorf("failed to get response log: %w", err)
}
err = item.Value(func(rawReslog []byte) error {
var resLog reqlog.ResponseLog
err = gob.NewDecoder(bytes.NewReader(rawReslog)).Decode(&resLog)
if err != nil {
return fmt.Errorf("failed to decode response log: %w", err)
}
reqLog.Response = &resLog
return nil
})
if err != nil {
return reqlog.RequestLog{}, fmt.Errorf("failed to retrieve or parse response log value: %w", err)
}
return reqLog, nil
}
func (db *Database) FindRequestLogByID(ctx context.Context, reqLogID ulid.ULID) (reqLog reqlog.RequestLog, err error) {
txn := db.badger.NewTransaction(false)
defer txn.Discard()
reqLog, err = getRequestLogWithResponse(txn, reqLogID)
if err != nil {
return reqlog.RequestLog{}, fmt.Errorf("badger: failed to get request log: %w", err)
}
return reqLog, nil
}
func (db *Database) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(reqLog)
if err != nil {
return fmt.Errorf("badger: failed to encode request log: %w", err)
}
entries := []*badger.Entry{
// Request log itself.
{
Key: entryKey(reqLogPrefix, 0, reqLog.ID[:]),
Value: buf.Bytes(),
},
// Index by project ID.
{
Key: entryKey(reqLogPrefix, reqLogProjectIDIndex, append(reqLog.ProjectID[:], reqLog.ID[:]...)),
},
}
err = db.badger.Update(func(txn *badger.Txn) error {
for i := range entries {
err := txn.SetEntry(entries[i])
if err != nil {
return err
}
}
return nil
})
if err != nil {
return fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(resLog)
if err != nil {
return fmt.Errorf("badger: failed to encode response log: %w", err)
}
err = db.badger.Update(func(txn *badger.Txn) error {
return txn.SetEntry(&badger.Entry{
Key: entryKey(resLogPrefix, 0, reqLogID[:]),
Value: buf.Bytes(),
})
})
if err != nil {
return fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
// Note: this transaction is used just for reading; we use the `badger.WriteBatch`
// API to bulk delete items.
txn := db.badger.NewTransaction(false)
defer txn.Discard()
reqLogIDs, err := findRequestLogIDsByProjectID(txn, projectID)
if err != nil {
return fmt.Errorf("badger: failed to find request log IDs: %w", err)
}
writeBatch := db.badger.NewWriteBatch()
defer writeBatch.Cancel()
for _, reqLogID := range reqLogIDs {
// Delete request logs.
err := writeBatch.Delete(entryKey(reqLogPrefix, 0, reqLogID[:]))
if err != nil {
return fmt.Errorf("badger: failed to delete request log: %w", err)
}
// Delete related response log.
err = writeBatch.Delete(entryKey(resLogPrefix, 0, reqLogID[:]))
if err != nil {
return fmt.Errorf("badger: failed to delete request log: %w", err)
}
}
if err := writeBatch.Flush(); err != nil {
return fmt.Errorf("badger: failed to commit batch write: %w", err)
}
err = db.badger.DropPrefix(entryKey(reqLogPrefix, reqLogProjectIDIndex, projectID[:]))
if err != nil {
return fmt.Errorf("badger: failed to drop request log project ID index items: %w", err)
}
return nil
}
func findRequestLogIDsByProjectID(txn *badger.Txn, projectID ulid.ULID) ([]ulid.ULID, error) {
reqLogIDs := make([]ulid.ULID, 0)
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
opts.Reverse = true
iterator := txn.NewIterator(opts)
defer iterator.Close()
var projectIndexKey []byte
prefix := entryKey(reqLogPrefix, reqLogProjectIDIndex, projectID[:])
for iterator.Seek(append(prefix, 255)); iterator.ValidForPrefix(prefix); iterator.Next() {
projectIndexKey = iterator.Item().KeyCopy(projectIndexKey)
var id ulid.ULID
// The request log ID starts *after* the first 2 prefix and index bytes
// and the 16 byte project ID.
if err := id.UnmarshalBinary(projectIndexKey[18:]); err != nil {
return nil, fmt.Errorf("failed to parse request log ID: %w", err)
}
reqLogIDs = append(reqLogIDs, id)
}
return reqLogIDs, nil
}

View File

@ -1,240 +0,0 @@
package badger
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/dgraph-io/badger/v3"
"github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/sender"
)
func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(req)
if err != nil {
return fmt.Errorf("badger: failed to encode sender request: %w", err)
}
entries := []*badger.Entry{
// Sender request itself.
{
Key: entryKey(senderReqPrefix, 0, req.ID[:]),
Value: buf.Bytes(),
},
// Index by project ID.
{
Key: entryKey(senderReqPrefix, senderReqProjectIDIndex, append(req.ProjectID[:], req.ID[:]...)),
},
}
err = db.badger.Update(func(txn *badger.Txn) error {
for i := range entries {
err := txn.SetEntry(entries[i])
if err != nil {
return err
}
}
return nil
})
if err != nil {
return fmt.Errorf("badger: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) FindSenderRequestByID(ctx context.Context, senderReqID ulid.ULID) (sender.Request, error) {
txn := db.badger.NewTransaction(false)
defer txn.Discard()
req, err := getSenderRequestWithResponseLog(txn, senderReqID)
if err != nil {
return sender.Request{}, fmt.Errorf("badger: failed to get sender request: %w", err)
}
return req, nil
}
func (db *Database) FindSenderRequests(
ctx context.Context,
filter sender.FindRequestsFilter,
scope *scope.Scope,
) ([]sender.Request, error) {
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
return nil, sender.ErrProjectIDMustBeSet
}
txn := db.badger.NewTransaction(false)
defer txn.Discard()
senderReqIDs, err := findSenderRequestIDsByProjectID(txn, filter.ProjectID)
if err != nil {
return nil, fmt.Errorf("badger: failed to find sender request IDs: %w", err)
}
senderReqs := make([]sender.Request, 0, len(senderReqIDs))
for _, id := range senderReqIDs {
senderReq, err := getSenderRequestWithResponseLog(txn, id)
if err != nil {
return nil, fmt.Errorf("badger: failed to get sender request (id: %v): %w", id.String(), err)
}
if filter.OnlyInScope {
if !senderReq.MatchScope(scope) {
continue
}
}
// Filter by search expression.
// TODO: Once pagination is introduced, this filter logic should be done
// as items are retrieved (e.g. when using a `badger.Iterator`).
if filter.SearchExpr != nil {
match, err := senderReq.Matches(filter.SearchExpr)
if err != nil {
return nil, fmt.Errorf(
"badger: failed to match search expression for sender request (id: %v): %w",
id.String(), err,
)
}
if !match {
continue
}
}
senderReqs = append(senderReqs, senderReq)
}
return senderReqs, nil
}
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
// Note: this transaction is used just for reading; we use the `badger.WriteBatch`
// API to bulk delete items.
txn := db.badger.NewTransaction(false)
defer txn.Discard()
senderReqIDs, err := findSenderRequestIDsByProjectID(txn, projectID)
if err != nil {
return fmt.Errorf("badger: failed to find sender request IDs: %w", err)
}
writeBatch := db.badger.NewWriteBatch()
defer writeBatch.Cancel()
for _, senderReqID := range senderReqIDs {
// Delete sender requests.
err := writeBatch.Delete(entryKey(senderReqPrefix, 0, senderReqID[:]))
if err != nil {
return fmt.Errorf("badger: failed to delete sender requests: %w", err)
}
// Delete related response log.
err = writeBatch.Delete(entryKey(resLogPrefix, 0, senderReqID[:]))
if err != nil {
return fmt.Errorf("badger: failed to delete request log: %w", err)
}
}
if err := writeBatch.Flush(); err != nil {
return fmt.Errorf("badger: failed to commit batch write: %w", err)
}
err = db.badger.DropPrefix(entryKey(senderReqPrefix, senderReqProjectIDIndex, projectID[:]))
if err != nil {
return fmt.Errorf("badger: failed to drop sender request project ID index items: %w", err)
}
return nil
}
func getSenderRequestWithResponseLog(txn *badger.Txn, senderReqID ulid.ULID) (sender.Request, error) {
item, err := txn.Get(entryKey(senderReqPrefix, 0, senderReqID[:]))
switch {
case errors.Is(err, badger.ErrKeyNotFound):
return sender.Request{}, sender.ErrRequestNotFound
case err != nil:
return sender.Request{}, fmt.Errorf("failed to lookup sender request item: %w", err)
}
req := sender.Request{
ID: senderReqID,
}
err = item.Value(func(rawSenderReq []byte) error {
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
if err != nil {
return fmt.Errorf("failed to decode sender request: %w", err)
}
return nil
})
if err != nil {
return sender.Request{}, fmt.Errorf("failed to retrieve or parse sender request value: %w", err)
}
item, err = txn.Get(entryKey(resLogPrefix, 0, senderReqID[:]))
if errors.Is(err, badger.ErrKeyNotFound) {
return req, nil
}
if err != nil {
return sender.Request{}, fmt.Errorf("failed to get response log: %w", err)
}
err = item.Value(func(rawReslog []byte) error {
var resLog reqlog.ResponseLog
err = gob.NewDecoder(bytes.NewReader(rawReslog)).Decode(&resLog)
if err != nil {
return fmt.Errorf("failed to decode response log: %w", err)
}
req.Response = &resLog
return nil
})
if err != nil {
return sender.Request{}, fmt.Errorf("failed to retrieve or parse response log value: %w", err)
}
return req, nil
}
func findSenderRequestIDsByProjectID(txn *badger.Txn, projectID ulid.ULID) ([]ulid.ULID, error) {
senderReqIDs := make([]ulid.ULID, 0)
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
opts.Reverse = true
iterator := txn.NewIterator(opts)
defer iterator.Close()
var projectIndexKey []byte
prefix := entryKey(senderReqPrefix, senderReqProjectIDIndex, projectID[:])
for iterator.Seek(append(prefix, 255)); iterator.ValidForPrefix(prefix); iterator.Next() {
projectIndexKey = iterator.Item().KeyCopy(projectIndexKey)
var id ulid.ULID
// The request log ID starts *after* the first 2 prefix and index bytes
// and the 16 byte project ID.
if err := id.UnmarshalBinary(projectIndexKey[18:]); err != nil {
return nil, fmt.Errorf("failed to parse sender request ID: %w", err)
}
senderReqIDs = append(senderReqIDs, id)
}
return senderReqIDs, nil
}

60
pkg/db/bolt/bolt.go Normal file
View File

@ -0,0 +1,60 @@
package bolt
import (
"fmt"
bolt "go.etcd.io/bbolt"
)
// Database is used to store and retrieve data from an underlying Bolt database.
type Database struct {
bolt *bolt.DB
}
// OpenDatabase opens a new Bolt database.
func OpenDatabase(path string, opts *bolt.Options) (*Database, error) {
db, err := bolt.Open(path, 0o600, opts)
if err != nil {
return nil, fmt.Errorf("bolt: failed to open database: %w", err)
}
return DatabaseFromBoltDB(db)
}
// Close closes the underlying Bolt database.
func (db *Database) Close() error {
return db.bolt.Close()
}
// DatabaseFromBoltDB returns a Database with `db` set as the underlying Bolt
// database.
func DatabaseFromBoltDB(db *bolt.DB) (*Database, error) {
err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(projectsBucketName)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, fmt.Errorf("bolt: failed to create projects bucket: %w", err)
}
return &Database{bolt: db}, nil
}
func createNestedBucket(tx *bolt.Tx, names ...[]byte) (b *bolt.Bucket, err error) {
for i, name := range names {
if b == nil {
b, err = tx.CreateBucketIfNotExists(name)
} else {
b, err = b.CreateBucketIfNotExists(name)
}
if err != nil {
return nil, fmt.Errorf("bolt: failed to create nested bucket %q: %w", names[:i+1], err)
}
}
return b, nil
}

23
pkg/db/bolt/logger.go Normal file
View File

@ -0,0 +1,23 @@
package bolt
import (
bolt "go.etcd.io/bbolt"
"go.uber.org/zap"
)
// Interface guard.
var _ bolt.Logger = (*Logger)(nil)
type Logger struct {
*zap.SugaredLogger
}
// Warning implements bbolt.Logger.
func (l *Logger) Warning(v ...interface{}) {
l.Warn(v...)
}
// Warningf implements bbolt.Logger.
func (l *Logger) Warningf(format string, v ...interface{}) {
l.Warnf(format, v...)
}

176
pkg/db/bolt/proj.go Normal file
View File

@ -0,0 +1,176 @@
package bolt
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/oklog/ulid"
bolt "go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/proj"
)
var (
ErrProjectsBucketNotFound = errors.New("bolt: projects bucket not found")
ErrProjectBucketNotFound = errors.New("bolt: project bucket not found")
)
var (
projectsBucketName = []byte("projects")
projectKey = []byte("project")
)
func projectsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
b := tx.Bucket(projectsBucketName)
if b == nil {
return nil, ErrProjectsBucketNotFound
}
return b, nil
}
func projectBucket(tx *bolt.Tx, projectID []byte) (*bolt.Bucket, error) {
pb, err := projectsBucket(tx)
if err != nil {
return nil, err
}
b := pb.Bucket(projectID[:])
if b == nil {
return nil, ErrProjectBucketNotFound
}
return b, nil
}
func (db *Database) UpsertProject(ctx context.Context, project proj.Project) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(project)
if err != nil {
return fmt.Errorf("bolt: failed to encode project: %w", err)
}
err = db.bolt.Update(func(tx *bolt.Tx) error {
b, err := createNestedBucket(tx, projectsBucketName, project.ID[:])
if err != nil {
return fmt.Errorf("bolt: failed to create project bucket: %w", err)
}
err = b.Put(projectKey, buf.Bytes())
if err != nil {
return fmt.Errorf("bolt: failed to upsert project: %w", err)
}
_, err = b.CreateBucketIfNotExists(reqLogsBucketName)
if err != nil {
return fmt.Errorf("bolt: failed to create request logs bucket: %w", err)
}
_, err = b.CreateBucketIfNotExists(senderReqsBucketName)
if err != nil {
return fmt.Errorf("bolt: failed to create sender requests bucket: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) FindProjectByID(ctx context.Context, projectID ulid.ULID) (project proj.Project, err error) {
err = db.bolt.View(func(tx *bolt.Tx) error {
bucket, err := projectBucket(tx, projectID[:])
if errors.Is(err, ErrProjectsBucketNotFound) || errors.Is(err, ErrProjectBucketNotFound) {
return proj.ErrProjectNotFound
}
if err != nil {
return err
}
rawProject := bucket.Get(projectKey)
if rawProject == nil {
return proj.ErrProjectNotFound
}
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
if err != nil {
return fmt.Errorf("failed to decode project: %w", err)
}
return nil
})
if err != nil {
return proj.Project{}, fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return project, nil
}
func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
err := db.bolt.Update(func(tx *bolt.Tx) error {
pb, err := projectsBucket(tx)
if err != nil {
return err
}
err = pb.DeleteBucket(projectID[:])
if err != nil {
return fmt.Errorf("failed to delete project bucket: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
projects := make([]proj.Project, 0)
err := db.bolt.View(func(tx *bolt.Tx) error {
pb, err := projectsBucket(tx)
if err != nil {
return err
}
err = pb.ForEachBucket(func(projectID []byte) error {
bucket, err := projectBucket(tx, projectID)
if err != nil {
return err
}
rawProject := bucket.Get(projectKey)
if rawProject == nil {
return proj.ErrProjectNotFound
}
var project proj.Project
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
if err != nil {
return fmt.Errorf("bolt: failed to decode project: %w", err)
}
projects = append(projects, project)
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to iterate over projects: %w", err)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return projects, nil
}

267
pkg/db/bolt/proj_test.go Normal file
View File

@ -0,0 +1,267 @@
package bolt_test
import (
"bytes"
"context"
"encoding/gob"
"errors"
"math/rand"
"regexp"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/oklog/ulid"
"go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/scope"
)
//nolint:gosec
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
var regexpCompareOpt = cmp.Comparer(func(x, y *regexp.Regexp) bool {
switch {
case x == nil && y == nil:
return true
case x == nil || y == nil:
return false
default:
return x.String() == y.String()
}
})
func TestUpsertProject(t *testing.T) {
t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
searchExpr, err := filter.ParseQuery("foo AND bar OR NOT baz")
if err != nil {
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
}
exp := proj.Project{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: "foobar",
Settings: proj.Settings{
ReqLogBypassOutOfScope: true,
ReqLogOnlyFindInScope: true,
ReqLogSearchExpr: searchExpr,
ScopeRules: []scope.Rule{
{
URL: regexp.MustCompile("^https://(.*)example.com(.*)$"),
Header: scope.Header{
Key: regexp.MustCompile("^X-Foo(.*)$"),
Value: regexp.MustCompile("^foo(.*)$"),
},
Body: regexp.MustCompile("^foo(.*)"),
},
},
},
}
err = db.UpsertProject(context.Background(), exp)
if err != nil {
t.Fatalf("unexpected error storing project: %v", err)
}
var rawProject []byte
err = boltDB.View(func(tx *bbolt.Tx) error {
rawProject = tx.Bucket([]byte("projects")).Bucket(exp.ID[:]).Get([]byte("project"))
return nil
})
if err != nil {
t.Fatalf("unexpected error retrieving project from database: %v", err)
}
if rawProject == nil {
t.Fatalf("expected raw project to be retrieved, got: nil")
}
got := proj.Project{}
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&got)
if err != nil {
t.Fatalf("unexpected error decoding project: %v", err)
}
if diff := cmp.Diff(exp, got, regexpCompareOpt, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
}
}
func TestFindProjectByID(t *testing.T) {
t.Parallel()
t.Run("existing project", func(t *testing.T) {
t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
exp := proj.Project{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
}
buf := bytes.Buffer{}
err = gob.NewEncoder(&buf).Encode(exp)
if err != nil {
t.Fatalf("unexpected error encoding project: %v", err)
}
err = boltDB.Update(func(tx *bbolt.Tx) error {
b, err := tx.Bucket([]byte("projects")).CreateBucket(exp.ID[:])
if err != nil {
return err
}
return b.Put([]byte("project"), buf.Bytes())
})
if err != nil {
t.Fatalf("unexpected error setting project: %v", err)
}
got, err := db.FindProjectByID(context.Background(), exp.ID)
if err != nil {
t.Fatalf("unexpected error finding project: %v", err)
}
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
}
})
t.Run("project not found", func(t *testing.T) {
t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
_, err = db.FindProjectByID(context.Background(), projectID)
if !errors.Is(err, proj.ErrProjectNotFound) {
t.Fatalf("expected `proj.ErrProjectNotFound`, got: %v", err)
}
})
}
func TestDeleteProject(t *testing.T) {
t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
// Insert test fixture.
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
})
if err != nil {
t.Fatalf("unexpected error storing project: %v", err)
}
err = db.DeleteProject(context.Background(), projectID)
if err != nil {
t.Fatalf("unexpected error deleting project: %v", err)
}
var got *bbolt.Bucket
err = boltDB.View(func(tx *bbolt.Tx) error {
got = tx.Bucket([]byte("projects")).Bucket(projectID[:])
return nil
})
if got != nil {
t.Fatalf("expected bucket to be nil, got: %v", got)
}
}
func TestProjects(t *testing.T) {
t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
exp := []proj.Project{
{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: "one",
},
{
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
Name: "two",
},
}
// Store fixtures.
for _, project := range exp {
err = db.UpsertProject(context.Background(), project)
if err != nil {
t.Fatalf("unexpected error creating project fixture: %v", err)
}
}
got, err := db.Projects(context.Background())
if err != nil {
t.Fatalf("unexpected error finding projects: %v", err)
}
if len(exp) != len(got) {
t.Fatalf("expected %v projects, got: %v", len(exp), len(got))
}
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
t.Fatalf("projects not equal (-exp, +got):\n%v", diff)
}
}

204
pkg/db/bolt/reqlog.go Normal file
View File

@ -0,0 +1,204 @@
package bolt
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/oklog/ulid"
bolt "go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
)
var ErrRequestLogsBucketNotFound = errors.New("bolt: request logs bucket not found")
var reqLogsBucketName = []byte("request_logs")
func requestLogsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
pb, err := projectBucket(tx, projectID[:])
if err != nil {
return nil, err
}
b := pb.Bucket(reqLogsBucketName)
if b == nil {
return nil, ErrRequestLogsBucketNotFound
}
return b, nil
}
func (db *Database) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scope *scope.Scope) (reqLogs []reqlog.RequestLog, err error) {
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
return nil, reqlog.ErrProjectIDMustBeSet
}
tx, err := db.bolt.Begin(false)
if err != nil {
return nil, fmt.Errorf("bolt: failed to begin transaction: %w", err)
}
defer tx.Rollback()
b, err := requestLogsBucket(tx, filter.ProjectID)
if err != nil {
return nil, fmt.Errorf("bolt: failed to get request logs bucket: %w", err)
}
err = b.ForEach(func(reqLogID, rawReqLog []byte) error {
var reqLog reqlog.RequestLog
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
if err != nil {
return fmt.Errorf("failed to decode request log: %w", err)
}
if filter.OnlyInScope && !reqLog.MatchScope(scope) {
return nil
}
// Filter by search expression. TODO: Once pagination is introduced,
// this filter logic should be done as items are retrieved.
if filter.SearchExpr != nil {
match, err := reqLog.Matches(filter.SearchExpr)
if err != nil {
return fmt.Errorf("failed to match search expression for request log (id: %v): %w", reqLogID, err)
}
if !match {
return nil
}
}
reqLogs = append(reqLogs, reqLog)
return nil
})
if err != nil {
return nil, fmt.Errorf("bolt: failed to iterate over request logs: %w", err)
}
// Reverse items, so newest requests appear first.
for i, j := 0, len(reqLogs)-1; i < j; i, j = i+1, j-1 {
reqLogs[i], reqLogs[j] = reqLogs[j], reqLogs[i]
}
return reqLogs, nil
}
func (db *Database) FindRequestLogByID(ctx context.Context, projectID, reqLogID ulid.ULID) (reqLog reqlog.RequestLog, err error) {
err = db.bolt.View(func(tx *bolt.Tx) error {
b, err := requestLogsBucket(tx, projectID)
if err != nil {
return fmt.Errorf("bolt: failed to get request logs bucket: %w", err)
}
rawReqLog := b.Get(reqLogID[:])
if rawReqLog == nil {
return reqlog.ErrRequestNotFound
}
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
if err != nil {
return fmt.Errorf("failed to decode request log: %w", err)
}
return nil
})
if err != nil {
return reqlog.RequestLog{}, fmt.Errorf("bolt: failed to find request log by ID: %w", err)
}
return reqLog, nil
}
func (db *Database) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(reqLog)
if err != nil {
return fmt.Errorf("bolt: failed to encode request log: %w", err)
}
err = db.bolt.Update(func(txn *bolt.Tx) error {
b, err := requestLogsBucket(txn, reqLog.ProjectID)
if err != nil {
return fmt.Errorf("failed to get request logs bucket: %w", err)
}
err = b.Put(reqLog.ID[:], buf.Bytes())
if err != nil {
return fmt.Errorf("failed to put request log: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(resLog)
if err != nil {
return fmt.Errorf("bolt: failed to encode response log: %w", err)
}
err = db.bolt.Update(func(txn *bolt.Tx) error {
b, err := requestLogsBucket(txn, projectID)
if err != nil {
return fmt.Errorf("failed to get request logs bucket: %w", err)
}
rawReqLog := b.Get(reqLogID[:])
if rawReqLog == nil {
return reqlog.ErrRequestNotFound
}
var reqLog reqlog.RequestLog
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
if err != nil {
return fmt.Errorf("failed to decode request log: %w", err)
}
reqLog.Response = &resLog
buf := bytes.Buffer{}
err = gob.NewEncoder(&buf).Encode(reqLog)
if err != nil {
return fmt.Errorf("failed to encode request log: %w", err)
}
err = b.Put(reqLog.ID[:], buf.Bytes())
if err != nil {
return fmt.Errorf("failed to put request log: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
err := db.bolt.Update(func(txn *bolt.Tx) error {
pb, err := projectBucket(txn, projectID[:])
if err != nil {
return fmt.Errorf("failed to get project bucket: %w", err)
}
return pb.DeleteBucket(reqLogsBucketName)
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}

View File

@ -1,4 +1,4 @@
package badger package bolt_test
import ( import (
"context" "context"
@ -8,10 +8,12 @@ import (
"testing" "testing"
"time" "time"
badgerdb "github.com/dgraph-io/badger/v3"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
) )
@ -21,15 +23,21 @@ func TestFindRequestLogs(t *testing.T) {
t.Run("without project ID in filter", func(t *testing.T) { t.Run("without project ID in filter", func(t *testing.T) {
t.Parallel() t.Parallel()
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true)) path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil { if err != nil {
t.Fatalf("failed to open badger database: %v", err) t.Fatalf("failed to open bolt database: %v", err)
} }
defer database.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
filter := reqlog.FindRequestsFilter{} filter := reqlog.FindRequestsFilter{}
_, err = database.FindRequestLogs(context.Background(), filter, nil) _, err = db.FindRequestLogs(context.Background(), filter, nil)
if !errors.Is(err, reqlog.ErrProjectIDMustBeSet) { if !errors.Is(err, reqlog.ErrProjectIDMustBeSet) {
t.Fatalf("expected `reqlog.ErrProjectIDMustBeSet`, got: %v", err) t.Fatalf("expected `reqlog.ErrProjectIDMustBeSet`, got: %v", err)
} }
@ -38,14 +46,27 @@ func TestFindRequestLogs(t *testing.T) {
t.Run("returns request logs and related response logs", func(t *testing.T) { t.Run("returns request logs and related response logs", func(t *testing.T) {
t.Parallel() t.Parallel()
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true)) path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil { if err != nil {
t.Fatalf("failed to open badger database: %v", err) t.Fatalf("failed to open bolt database: %v", err)
} }
defer database.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
fixtures := []reqlog.RequestLog{ fixtures := []reqlog.RequestLog{
{ {
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
@ -81,24 +102,17 @@ func TestFindRequestLogs(t *testing.T) {
// Store fixtures. // Store fixtures.
for _, reqLog := range fixtures { for _, reqLog := range fixtures {
err = database.StoreRequestLog(context.Background(), reqLog) err = db.StoreRequestLog(context.Background(), reqLog)
if err != nil { if err != nil {
t.Fatalf("unexpected error creating request log fixture: %v", err) t.Fatalf("unexpected error creating request log fixture: %v", err)
} }
if reqLog.Response != nil {
err = database.StoreResponseLog(context.Background(), reqLog.ID, *reqLog.Response)
if err != nil {
t.Fatalf("unexpected error creating response log fixture: %v", err)
}
}
} }
filter := reqlog.FindRequestsFilter{ filter := reqlog.FindRequestsFilter{
ProjectID: projectID, ProjectID: projectID,
} }
got, err := database.FindRequestLogs(context.Background(), filter, nil) got, err := db.FindRequestLogs(context.Background(), filter, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error finding request logs: %v", err) t.Fatalf("unexpected error finding request logs: %v", err)
} }

172
pkg/db/bolt/sender.go Normal file
View File

@ -0,0 +1,172 @@
package bolt
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"github.com/oklog/ulid"
bolt "go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/sender"
)
var ErrSenderRequestsBucketNotFound = errors.New("bolt: sender requests bucket not found")
var senderReqsBucketName = []byte("sender_requests")
func senderReqsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
pb, err := projectBucket(tx, projectID[:])
if err != nil {
return nil, err
}
b := pb.Bucket(senderReqsBucketName)
if b == nil {
return nil, ErrSenderRequestsBucketNotFound
}
return b, nil
}
func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request) error {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(req)
if err != nil {
return fmt.Errorf("bolt: failed to encode sender request: %w", err)
}
err = db.bolt.Update(func(tx *bolt.Tx) error {
senderReqsBucket, err := senderReqsBucket(tx, req.ProjectID)
if err != nil {
return fmt.Errorf("failed to get sender requests bucket: %w", err)
}
err = senderReqsBucket.Put(req.ID[:], buf.Bytes())
if err != nil {
return fmt.Errorf("failed to put sender request: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}
func (db *Database) FindSenderRequestByID(ctx context.Context, projectID, senderReqID ulid.ULID) (req sender.Request, err error) {
if projectID.Compare(ulid.ULID{}) == 0 {
return sender.Request{}, sender.ErrProjectIDMustBeSet
}
err = db.bolt.View(func(tx *bolt.Tx) error {
senderReqsBucket, err := senderReqsBucket(tx, projectID)
if err != nil {
return fmt.Errorf("failed to get sender requests bucket: %w", err)
}
rawSenderReq := senderReqsBucket.Get(senderReqID[:])
if rawSenderReq == nil {
return sender.ErrRequestNotFound
}
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
if err != nil {
return fmt.Errorf("failed to decode sender request: %w", err)
}
return nil
})
if err != nil {
return sender.Request{}, fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return req, nil
}
func (db *Database) FindSenderRequests(ctx context.Context, filter sender.FindRequestsFilter, scope *scope.Scope) (reqs []sender.Request, err error) {
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
return nil, sender.ErrProjectIDMustBeSet
}
tx, err := db.bolt.Begin(false)
if err != nil {
return nil, fmt.Errorf("bolt: failed to begin transaction: %w", err)
}
defer tx.Rollback()
b, err := senderReqsBucket(tx, filter.ProjectID)
if err != nil {
return nil, fmt.Errorf("failed to get sender requests bucket: %w", err)
}
err = b.ForEach(func(senderReqID, rawSenderReq []byte) error {
var req sender.Request
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
if err != nil {
return fmt.Errorf("failed to decode sender request: %w", err)
}
if filter.OnlyInScope {
if !req.MatchScope(scope) {
return nil
}
}
// Filter by search expression. TODO: Once pagination is introduced,
// this filter logic should be done as items are retrieved.
if filter.SearchExpr != nil {
match, err := req.Matches(filter.SearchExpr)
if err != nil {
return fmt.Errorf(
"bolt: failed to match search expression for sender request (id: %v): %w",
senderReqID, err,
)
}
if !match {
return nil
}
}
reqs = append(reqs, req)
return nil
})
if err != nil {
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
// Reverse items, so newest requests appear first.
for i, j := 0, len(reqs)-1; i < j; i, j = i+1, j-1 {
reqs[i], reqs[j] = reqs[j], reqs[i]
}
return reqs, nil
}
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
err := db.bolt.Update(func(tx *bolt.Tx) error {
senderReqsBucket, err := senderReqsBucket(tx, projectID)
if err != nil {
return fmt.Errorf("failed to get sender requests bucket: %w", err)
}
err = senderReqsBucket.DeleteBucket(senderReqsBucketName)
if err != nil {
return fmt.Errorf("failed to delete sender requests bucket: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
}
return nil
}

View File

@ -1,26 +1,23 @@
package badger_test package bolt_test
import ( import (
"context" "context"
"errors" "errors"
"math/rand"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"time" "time"
badgerdb "github.com/dgraph-io/badger/v3"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/db/badger" "github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/sender" "github.com/dstotijn/hetty/pkg/sender"
) )
//nolint:gosec
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
var exampleURL = func() *url.URL { var exampleURL = func() *url.URL {
u, err := url.Parse("https://example.com/foobar") u, err := url.Parse("https://example.com/foobar")
if err != nil { if err != nil {
@ -33,18 +30,35 @@ var exampleURL = func() *url.URL {
func TestFindRequestByID(t *testing.T) { func TestFindRequestByID(t *testing.T) {
t.Parallel() t.Parallel()
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true)) path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil { if err != nil {
t.Fatalf("failed to open badger database: %v", err) t.Fatalf("failed to open bolt database: %v", err)
}
defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
} }
defer database.Close()
// See: https://go.dev/blog/subtests#cleaning-up-after-a-group-of-parallel-tests // See: https://go.dev/blog/subtests#cleaning-up-after-a-group-of-parallel-tests
t.Run("group", func(t *testing.T) { t.Run("group", func(t *testing.T) {
t.Run("sender request not found", func(t *testing.T) { t.Run("sender request not found", func(t *testing.T) {
t.Parallel() t.Parallel()
_, err := database.FindSenderRequestByID(context.Background(), ulid.ULID{}) _, err := db.FindSenderRequestByID(context.Background(), projectID, reqID)
if !errors.Is(err, sender.ErrRequestNotFound) { if !errors.Is(err, sender.ErrRequestNotFound) {
t.Fatalf("expected `sender.ErrRequestNotFound`, got: %v", err) t.Fatalf("expected `sender.ErrRequestNotFound`, got: %v", err)
} }
@ -55,7 +69,7 @@ func TestFindRequestByID(t *testing.T) {
exp := sender.Request{ exp := sender.Request{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ProjectID: projectID,
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
URL: exampleURL, URL: exampleURL,
@ -65,31 +79,23 @@ func TestFindRequestByID(t *testing.T) {
"X-Foo": []string{"bar"}, "X-Foo": []string{"bar"},
}, },
Body: []byte("foo"), Body: []byte("foo"),
} Response: &reqlog.ResponseLog{
Proto: "HTTP/2.0",
err := database.StoreSenderRequest(context.Background(), exp) Status: "200 OK",
if err != nil { StatusCode: 200,
t.Fatalf("unexpected error (expected: nil, got: %v)", err) Header: http.Header{
} "X-Yolo": []string{"swag"},
},
resLog := reqlog.ResponseLog{ Body: []byte("bar"),
Proto: "HTTP/2.0",
Status: "200 OK",
StatusCode: 200,
Header: http.Header{
"X-Yolo": []string{"swag"},
}, },
Body: []byte("bar"),
} }
err = database.StoreResponseLog(context.Background(), exp.ID, resLog) err := db.StoreSenderRequest(context.Background(), exp)
if err != nil { if err != nil {
t.Fatalf("unexpected error (expected: nil, got: %v)", err) t.Fatalf("unexpected error (expected: nil, got: %v)", err)
} }
exp.Response = &resLog got, err := db.FindSenderRequestByID(context.Background(), exp.ProjectID, exp.ID)
got, err := database.FindSenderRequestByID(context.Background(), exp.ID)
if err != nil { if err != nil {
t.Fatalf("unexpected error (expected: nil, got: %v)", err) t.Fatalf("unexpected error (expected: nil, got: %v)", err)
} }
@ -107,15 +113,22 @@ func TestFindSenderRequests(t *testing.T) {
t.Run("without project ID in filter", func(t *testing.T) { t.Run("without project ID in filter", func(t *testing.T) {
t.Parallel() t.Parallel()
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true)) path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil { if err != nil {
t.Fatalf("failed to open badger database: %v", err) t.Fatalf("failed to open bolt database: %v", err)
} }
defer database.Close() defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
filter := sender.FindRequestsFilter{} filter := sender.FindRequestsFilter{}
_, err = database.FindSenderRequests(context.Background(), filter, nil) _, err = db.FindSenderRequests(context.Background(), filter, nil)
if !errors.Is(err, sender.ErrProjectIDMustBeSet) { if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err) t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
} }
@ -124,14 +137,30 @@ func TestFindSenderRequests(t *testing.T) {
t.Run("returns sender requests and related response logs", func(t *testing.T) { t.Run("returns sender requests and related response logs", func(t *testing.T) {
t.Parallel() t.Parallel()
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true)) path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil { if err != nil {
t.Fatalf("failed to open badger database: %v", err) t.Fatalf("failed to open bolt database: %v", err)
} }
defer database.Close() defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
Name: "foobar",
Settings: proj.Settings{},
})
if err != nil {
t.Fatalf("unexpected error creating project (expected: nil, got: %v)", err)
}
fixtures := []sender.Request{ fixtures := []sender.Request{
{ {
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
@ -169,24 +198,17 @@ func TestFindSenderRequests(t *testing.T) {
// Store fixtures. // Store fixtures.
for _, senderReq := range fixtures { for _, senderReq := range fixtures {
err = database.StoreSenderRequest(context.Background(), senderReq) err = db.StoreSenderRequest(context.Background(), senderReq)
if err != nil { if err != nil {
t.Fatalf("unexpected error creating request log fixture: %v", err) t.Fatalf("unexpected error creating request log fixture: %v", err)
} }
if senderReq.Response != nil {
err = database.StoreResponseLog(context.Background(), senderReq.ID, *senderReq.Response)
if err != nil {
t.Fatalf("unexpected error creating response log fixture: %v", err)
}
}
} }
filter := sender.FindRequestsFilter{ filter := sender.FindRequestsFilter{
ProjectID: projectID, ProjectID: projectID,
} }
got, err := database.FindSenderRequests(context.Background(), filter, nil) got, err := db.FindSenderRequests(context.Background(), filter, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error finding sender requests: %v", err) t.Fatalf("unexpected error finding sender requests: %v", err)
} }

View File

@ -21,27 +21,11 @@ import (
//nolint:gosec //nolint:gosec
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano())) var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
// Service is used for managing projects. type Service struct {
type Service interface {
CreateProject(ctx context.Context, name string) (Project, error)
OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error)
CloseProject() error
DeleteProject(ctx context.Context, projectID ulid.ULID) error
ActiveProject(ctx context.Context) (Project, error)
IsProjectActive(projectID ulid.ULID) bool
Projects(ctx context.Context) ([]Project, error)
Scope() *scope.Scope
SetScopeRules(ctx context.Context, rules []scope.Rule) error
SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error
SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error
UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error
}
type service struct {
repo Repository repo Repository
interceptSvc *intercept.Service interceptSvc *intercept.Service
reqLogSvc reqlog.Service reqLogSvc *reqlog.Service
senderSvc sender.Service senderSvc *sender.Service
scope *scope.Scope scope *scope.Scope
activeProjectID ulid.ULID activeProjectID ulid.ULID
mu sync.RWMutex mu sync.RWMutex
@ -87,14 +71,14 @@ var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`)
type Config struct { type Config struct {
Repository Repository Repository Repository
InterceptService *intercept.Service InterceptService *intercept.Service
ReqLogService reqlog.Service ReqLogService *reqlog.Service
SenderService sender.Service SenderService *sender.Service
Scope *scope.Scope Scope *scope.Scope
} }
// NewService returns a new Service. // NewService returns a new Service.
func NewService(cfg Config) (Service, error) { func NewService(cfg Config) (*Service, error) {
return &service{ return &Service{
repo: cfg.Repository, repo: cfg.Repository,
interceptSvc: cfg.InterceptService, interceptSvc: cfg.InterceptService,
reqLogSvc: cfg.ReqLogService, reqLogSvc: cfg.ReqLogService,
@ -103,7 +87,7 @@ func NewService(cfg Config) (Service, error) {
}, nil }, nil
} }
func (svc *service) CreateProject(ctx context.Context, name string) (Project, error) { func (svc *Service) CreateProject(ctx context.Context, name string) (Project, error) {
if !nameRegexp.MatchString(name) { if !nameRegexp.MatchString(name) {
return Project{}, ErrInvalidName return Project{}, ErrInvalidName
} }
@ -122,7 +106,7 @@ func (svc *service) CreateProject(ctx context.Context, name string) (Project, er
} }
// CloseProject closes the currently open project (if there is one). // CloseProject closes the currently open project (if there is one).
func (svc *service) CloseProject() error { func (svc *Service) CloseProject() error {
svc.mu.Lock() svc.mu.Lock()
defer svc.mu.Unlock() defer svc.mu.Unlock()
@ -148,7 +132,7 @@ func (svc *service) CloseProject() error {
} }
// DeleteProject removes a project from the repository. // DeleteProject removes a project from the repository.
func (svc *service) DeleteProject(ctx context.Context, projectID ulid.ULID) error { func (svc *Service) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
if svc.activeProjectID.Compare(projectID) == 0 { if svc.activeProjectID.Compare(projectID) == 0 {
return fmt.Errorf("proj: project (%v) is active", projectID.String()) return fmt.Errorf("proj: project (%v) is active", projectID.String())
} }
@ -161,7 +145,7 @@ func (svc *service) DeleteProject(ctx context.Context, projectID ulid.ULID) erro
} }
// OpenProject sets a project as the currently active project. // OpenProject sets a project as the currently active project.
func (svc *service) OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error) { func (svc *Service) OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error) {
svc.mu.Lock() svc.mu.Lock()
defer svc.mu.Unlock() defer svc.mu.Unlock()
@ -203,7 +187,7 @@ func (svc *service) OpenProject(ctx context.Context, projectID ulid.ULID) (Proje
return project, nil return project, nil
} }
func (svc *service) ActiveProject(ctx context.Context) (Project, error) { func (svc *Service) ActiveProject(ctx context.Context) (Project, error) {
activeProjectID := svc.activeProjectID activeProjectID := svc.activeProjectID
if activeProjectID.Compare(ulid.ULID{}) == 0 { if activeProjectID.Compare(ulid.ULID{}) == 0 {
return Project{}, ErrNoProject return Project{}, ErrNoProject
@ -219,7 +203,7 @@ func (svc *service) ActiveProject(ctx context.Context) (Project, error) {
return project, nil return project, nil
} }
func (svc *service) Projects(ctx context.Context) ([]Project, error) { func (svc *Service) Projects(ctx context.Context) ([]Project, error) {
projects, err := svc.repo.Projects(ctx) projects, err := svc.repo.Projects(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("proj: could not get projects: %w", err) return nil, fmt.Errorf("proj: could not get projects: %w", err)
@ -228,11 +212,11 @@ func (svc *service) Projects(ctx context.Context) ([]Project, error) {
return projects, nil return projects, nil
} }
func (svc *service) Scope() *scope.Scope { func (svc *Service) Scope() *scope.Scope {
return svc.scope return svc.scope
} }
func (svc *service) SetScopeRules(ctx context.Context, rules []scope.Rule) error { func (svc *Service) SetScopeRules(ctx context.Context, rules []scope.Rule) error {
project, err := svc.ActiveProject(ctx) project, err := svc.ActiveProject(ctx)
if err != nil { if err != nil {
return err return err
@ -250,7 +234,7 @@ func (svc *service) SetScopeRules(ctx context.Context, rules []scope.Rule) error
return nil return nil
} }
func (svc *service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error { func (svc *Service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error {
project, err := svc.ActiveProject(ctx) project, err := svc.ActiveProject(ctx)
if err != nil { if err != nil {
return err return err
@ -271,7 +255,7 @@ func (svc *service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.F
return nil return nil
} }
func (svc *service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error { func (svc *Service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error {
project, err := svc.ActiveProject(ctx) project, err := svc.ActiveProject(ctx)
if err != nil { if err != nil {
return err return err
@ -292,11 +276,11 @@ func (svc *service) SetSenderRequestFindFilter(ctx context.Context, filter sende
return nil return nil
} }
func (svc *service) IsProjectActive(projectID ulid.ULID) bool { func (svc *Service) IsProjectActive(projectID ulid.ULID) bool {
return projectID.Compare(svc.activeProjectID) == 0 return projectID.Compare(svc.activeProjectID) == 0
} }
func (svc *service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error { func (svc *Service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error {
project, err := svc.ActiveProject(ctx) project, err := svc.ActiveProject(ctx)
if err != nil { if err != nil {
return err return err

View File

@ -10,8 +10,8 @@ import (
type Repository interface { type Repository interface {
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error) FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) FindRequestLogByID(ctx context.Context, projectID, id ulid.ULID) (RequestLog, error)
StoreRequestLog(ctx context.Context, reqLog RequestLog) error StoreRequestLog(ctx context.Context, reqLog RequestLog) error
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog ResponseLog) error StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog ResponseLog) error
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
} }

View File

@ -1,291 +0,0 @@
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package reqlog_test
import (
"context"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
"github.com/oklog/ulid"
"sync"
)
// Ensure, that RepoMock does implement reqlog.Repository.
// If this is not the case, regenerate this file with moq.
var _ reqlog.Repository = &RepoMock{}
// RepoMock is a mock implementation of reqlog.Repository.
//
// func TestSomethingThatUsesRepository(t *testing.T) {
//
// // make and configure a mocked reqlog.Repository
// mockedRepository := &RepoMock{
// ClearRequestLogsFunc: func(ctx context.Context, projectID ulid.ULID) error {
// panic("mock out the ClearRequestLogs method")
// },
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
// panic("mock out the FindRequestLogByID method")
// },
// FindRequestLogsFunc: func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
// panic("mock out the FindRequestLogs method")
// },
// StoreRequestLogFunc: func(ctx context.Context, reqLog reqlog.RequestLog) error {
// panic("mock out the StoreRequestLog method")
// },
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
// panic("mock out the StoreResponseLog method")
// },
// }
//
// // use mockedRepository in code that requires reqlog.Repository
// // and then make assertions.
//
// }
type RepoMock struct {
// ClearRequestLogsFunc mocks the ClearRequestLogs method.
ClearRequestLogsFunc func(ctx context.Context, projectID ulid.ULID) error
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
// FindRequestLogsFunc mocks the FindRequestLogs method.
FindRequestLogsFunc func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error)
// StoreRequestLogFunc mocks the StoreRequestLog method.
StoreRequestLogFunc func(ctx context.Context, reqLog reqlog.RequestLog) error
// StoreResponseLogFunc mocks the StoreResponseLog method.
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
// calls tracks calls to the methods.
calls struct {
// ClearRequestLogs holds details about calls to the ClearRequestLogs method.
ClearRequestLogs []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ProjectID is the projectID argument value.
ProjectID ulid.ULID
}
// FindRequestLogByID holds details about calls to the FindRequestLogByID method.
FindRequestLogByID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ID is the id argument value.
ID ulid.ULID
}
// FindRequestLogs holds details about calls to the FindRequestLogs method.
FindRequestLogs []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Filter is the filter argument value.
Filter reqlog.FindRequestsFilter
// ScopeMoqParam is the scopeMoqParam argument value.
ScopeMoqParam *scope.Scope
}
// StoreRequestLog holds details about calls to the StoreRequestLog method.
StoreRequestLog []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ReqLog is the reqLog argument value.
ReqLog reqlog.RequestLog
}
// StoreResponseLog holds details about calls to the StoreResponseLog method.
StoreResponseLog []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ReqLogID is the reqLogID argument value.
ReqLogID ulid.ULID
// ResLog is the resLog argument value.
ResLog reqlog.ResponseLog
}
}
lockClearRequestLogs sync.RWMutex
lockFindRequestLogByID sync.RWMutex
lockFindRequestLogs sync.RWMutex
lockStoreRequestLog sync.RWMutex
lockStoreResponseLog sync.RWMutex
}
// ClearRequestLogs calls ClearRequestLogsFunc.
func (mock *RepoMock) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
if mock.ClearRequestLogsFunc == nil {
panic("RepoMock.ClearRequestLogsFunc: method is nil but Repository.ClearRequestLogs was just called")
}
callInfo := struct {
Ctx context.Context
ProjectID ulid.ULID
}{
Ctx: ctx,
ProjectID: projectID,
}
mock.lockClearRequestLogs.Lock()
mock.calls.ClearRequestLogs = append(mock.calls.ClearRequestLogs, callInfo)
mock.lockClearRequestLogs.Unlock()
return mock.ClearRequestLogsFunc(ctx, projectID)
}
// ClearRequestLogsCalls gets all the calls that were made to ClearRequestLogs.
// Check the length with:
// len(mockedRepository.ClearRequestLogsCalls())
func (mock *RepoMock) ClearRequestLogsCalls() []struct {
Ctx context.Context
ProjectID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ProjectID ulid.ULID
}
mock.lockClearRequestLogs.RLock()
calls = mock.calls.ClearRequestLogs
mock.lockClearRequestLogs.RUnlock()
return calls
}
// FindRequestLogByID calls FindRequestLogByIDFunc.
func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
if mock.FindRequestLogByIDFunc == nil {
panic("RepoMock.FindRequestLogByIDFunc: method is nil but Repository.FindRequestLogByID was just called")
}
callInfo := struct {
Ctx context.Context
ID ulid.ULID
}{
Ctx: ctx,
ID: id,
}
mock.lockFindRequestLogByID.Lock()
mock.calls.FindRequestLogByID = append(mock.calls.FindRequestLogByID, callInfo)
mock.lockFindRequestLogByID.Unlock()
return mock.FindRequestLogByIDFunc(ctx, id)
}
// FindRequestLogByIDCalls gets all the calls that were made to FindRequestLogByID.
// Check the length with:
// len(mockedRepository.FindRequestLogByIDCalls())
func (mock *RepoMock) FindRequestLogByIDCalls() []struct {
Ctx context.Context
ID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ID ulid.ULID
}
mock.lockFindRequestLogByID.RLock()
calls = mock.calls.FindRequestLogByID
mock.lockFindRequestLogByID.RUnlock()
return calls
}
// FindRequestLogs calls FindRequestLogsFunc.
func (mock *RepoMock) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
if mock.FindRequestLogsFunc == nil {
panic("RepoMock.FindRequestLogsFunc: method is nil but Repository.FindRequestLogs was just called")
}
callInfo := struct {
Ctx context.Context
Filter reqlog.FindRequestsFilter
ScopeMoqParam *scope.Scope
}{
Ctx: ctx,
Filter: filter,
ScopeMoqParam: scopeMoqParam,
}
mock.lockFindRequestLogs.Lock()
mock.calls.FindRequestLogs = append(mock.calls.FindRequestLogs, callInfo)
mock.lockFindRequestLogs.Unlock()
return mock.FindRequestLogsFunc(ctx, filter, scopeMoqParam)
}
// FindRequestLogsCalls gets all the calls that were made to FindRequestLogs.
// Check the length with:
// len(mockedRepository.FindRequestLogsCalls())
func (mock *RepoMock) FindRequestLogsCalls() []struct {
Ctx context.Context
Filter reqlog.FindRequestsFilter
ScopeMoqParam *scope.Scope
} {
var calls []struct {
Ctx context.Context
Filter reqlog.FindRequestsFilter
ScopeMoqParam *scope.Scope
}
mock.lockFindRequestLogs.RLock()
calls = mock.calls.FindRequestLogs
mock.lockFindRequestLogs.RUnlock()
return calls
}
// StoreRequestLog calls StoreRequestLogFunc.
func (mock *RepoMock) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
if mock.StoreRequestLogFunc == nil {
panic("RepoMock.StoreRequestLogFunc: method is nil but Repository.StoreRequestLog was just called")
}
callInfo := struct {
Ctx context.Context
ReqLog reqlog.RequestLog
}{
Ctx: ctx,
ReqLog: reqLog,
}
mock.lockStoreRequestLog.Lock()
mock.calls.StoreRequestLog = append(mock.calls.StoreRequestLog, callInfo)
mock.lockStoreRequestLog.Unlock()
return mock.StoreRequestLogFunc(ctx, reqLog)
}
// StoreRequestLogCalls gets all the calls that were made to StoreRequestLog.
// Check the length with:
// len(mockedRepository.StoreRequestLogCalls())
func (mock *RepoMock) StoreRequestLogCalls() []struct {
Ctx context.Context
ReqLog reqlog.RequestLog
} {
var calls []struct {
Ctx context.Context
ReqLog reqlog.RequestLog
}
mock.lockStoreRequestLog.RLock()
calls = mock.calls.StoreRequestLog
mock.lockStoreRequestLog.RUnlock()
return calls
}
// StoreResponseLog calls StoreResponseLogFunc.
func (mock *RepoMock) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
if mock.StoreResponseLogFunc == nil {
panic("RepoMock.StoreResponseLogFunc: method is nil but Repository.StoreResponseLog was just called")
}
callInfo := struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
}{
Ctx: ctx,
ReqLogID: reqLogID,
ResLog: resLog,
}
mock.lockStoreResponseLog.Lock()
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
mock.lockStoreResponseLog.Unlock()
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
}
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
// Check the length with:
// len(mockedRepository.StoreResponseLogCalls())
func (mock *RepoMock) StoreResponseLogCalls() []struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
} {
var calls []struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
}
mock.lockStoreResponseLog.RLock()
calls = mock.calls.StoreResponseLog
mock.lockStoreResponseLog.RUnlock()
return calls
}

View File

@ -51,21 +51,7 @@ type ResponseLog struct {
Body []byte Body []byte
} }
type Service interface { type Service struct {
FindRequests(ctx context.Context) ([]RequestLog, error)
FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error)
ClearRequests(ctx context.Context, projectID ulid.ULID) error
RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc
ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc
SetActiveProjectID(id ulid.ULID)
ActiveProjectID() ulid.ULID
SetBypassOutOfScopeRequests(bool)
BypassOutOfScopeRequests() bool
SetFindReqsFilter(filter FindRequestsFilter)
FindReqsFilter() FindRequestsFilter
}
type service struct {
bypassOutOfScopeRequests bool bypassOutOfScopeRequests bool
findReqsFilter FindRequestsFilter findReqsFilter FindRequestsFilter
activeProjectID ulid.ULID activeProjectID ulid.ULID
@ -81,16 +67,18 @@ type FindRequestsFilter struct {
} }
type Config struct { type Config struct {
Scope *scope.Scope ActiveProjectID ulid.ULID
Repository Repository Scope *scope.Scope
Logger log.Logger Repository Repository
Logger log.Logger
} }
func NewService(cfg Config) Service { func NewService(cfg Config) *Service {
s := &service{ s := &Service{
repo: cfg.Repository, activeProjectID: cfg.ActiveProjectID,
scope: cfg.Scope, repo: cfg.Repository,
logger: cfg.Logger, scope: cfg.Scope,
logger: cfg.Logger,
} }
if s.logger == nil { if s.logger == nil {
@ -100,28 +88,28 @@ func NewService(cfg Config) Service {
return s return s
} }
func (svc *service) FindRequests(ctx context.Context) ([]RequestLog, error) { func (svc *Service) FindRequests(ctx context.Context) ([]RequestLog, error) {
return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope) return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope)
} }
func (svc *service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) { func (svc *Service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
return svc.repo.FindRequestLogByID(ctx, id) return svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id)
} }
func (svc *service) ClearRequests(ctx context.Context, projectID ulid.ULID) error { func (svc *Service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
return svc.repo.ClearRequestLogs(ctx, projectID) return svc.repo.ClearRequestLogs(ctx, projectID)
} }
func (svc *service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error { func (svc *Service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
resLog, err := ParseHTTPResponse(res) resLog, err := ParseHTTPResponse(res)
if err != nil { if err != nil {
return err return err
} }
return svc.repo.StoreResponseLog(ctx, reqLogID, resLog) return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, resLog)
} }
func (svc *service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc { func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
return func(req *http.Request) { return func(req *http.Request) {
next(req) next(req)
@ -199,7 +187,7 @@ func (svc *service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
} }
} }
func (svc *service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc { func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
return func(res *http.Response) error { return func(res *http.Response) error {
if err := next(res); err != nil { if err := next(res); err != nil {
return err return err
@ -241,27 +229,27 @@ func (svc *service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
} }
} }
func (svc *service) SetActiveProjectID(id ulid.ULID) { func (svc *Service) SetActiveProjectID(id ulid.ULID) {
svc.activeProjectID = id svc.activeProjectID = id
} }
func (svc *service) ActiveProjectID() ulid.ULID { func (svc *Service) ActiveProjectID() ulid.ULID {
return svc.activeProjectID return svc.activeProjectID
} }
func (svc *service) SetFindReqsFilter(filter FindRequestsFilter) { func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
svc.findReqsFilter = filter svc.findReqsFilter = filter
} }
func (svc *service) FindReqsFilter() FindRequestsFilter { func (svc *Service) FindReqsFilter() FindRequestsFilter {
return svc.findReqsFilter return svc.findReqsFilter
} }
func (svc *service) SetBypassOutOfScopeRequests(bypass bool) { func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
svc.bypassOutOfScopeRequests = bypass svc.bypassOutOfScopeRequests = bypass
} }
func (svc *service) BypassOutOfScopeRequests() bool { func (svc *Service) BypassOutOfScopeRequests() bool {
return svc.bypassOutOfScopeRequests return svc.bypassOutOfScopeRequests
} }

View File

@ -1,7 +1,5 @@
package reqlog_test package reqlog_test
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg reqlog_test . Repository:RepoMock
import ( import (
"context" "context"
"io" "io"
@ -14,7 +12,10 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
@ -25,16 +26,32 @@ var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
//nolint:paralleltest //nolint:paralleltest
func TestRequestModifier(t *testing.T) { func TestRequestModifier(t *testing.T) {
repoMock := &RepoMock{ path := t.TempDir() + "bolt.db"
StoreRequestLogFunc: func(_ context.Context, _ reqlog.RequestLog) error { boltDB, err := bbolt.Open(path, 0o600, nil)
return nil if err != nil {
}, t.Fatalf("failed to open bolt database: %v", err)
} }
defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
svc := reqlog.NewService(reqlog.Config{ svc := reqlog.NewService(reqlog.Config{
Repository: repoMock, Repository: db,
Scope: &scope.Scope{}, Scope: &scope.Scope{},
}) })
svc.SetActiveProjectID(ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)) svc.SetActiveProjectID(projectID)
next := func(req *http.Request) { next := func(req *http.Request) {
req.Body = io.NopCloser(strings.NewReader("modified body")) req.Body = io.NopCloser(strings.NewReader("modified body"))
@ -47,13 +64,8 @@ func TestRequestModifier(t *testing.T) {
reqModFn(req) reqModFn(req)
t.Run("request log was stored in repository", func(t *testing.T) { t.Run("request log was stored in repository", func(t *testing.T) {
gotCount := len(repoMock.StoreRequestLogCalls())
if expCount := 1; expCount != gotCount {
t.Fatalf("incorrect `proj.Service.AddRequestLog` calls (expected: %v, got: %v)", expCount, gotCount)
}
exp := reqlog.RequestLog{ exp := reqlog.RequestLog{
ID: ulid.ULID{}, // Empty value ID: reqID,
ProjectID: svc.ActiveProjectID(), ProjectID: svc.ActiveProjectID(),
Method: req.Method, Method: req.Method,
URL: req.URL, URL: req.URL,
@ -61,8 +73,11 @@ func TestRequestModifier(t *testing.T) {
Header: req.Header, Header: req.Header,
Body: []byte("modified body"), Body: []byte("modified body"),
} }
got := repoMock.StoreRequestLogCalls()[0].ReqLog
got.ID = ulid.ULID{} // Override to empty value so we can compare against expected value. got, err := svc.FindRequestLogByID(context.Background(), reqID)
if err != nil {
t.Fatalf("failed to find request by id: %v", err)
}
if diff := cmp.Diff(exp, got); diff != "" { if diff := cmp.Diff(exp, got); diff != "" {
t.Fatalf("request log not equal (-exp, +got):\n%v", diff) t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
@ -72,15 +87,31 @@ func TestRequestModifier(t *testing.T) {
//nolint:paralleltest //nolint:paralleltest
func TestResponseModifier(t *testing.T) { func TestResponseModifier(t *testing.T) {
repoMock := &RepoMock{ path := t.TempDir() + "bolt.db"
StoreResponseLogFunc: func(_ context.Context, _ ulid.ULID, _ reqlog.ResponseLog) error { boltDB, err := bbolt.Open(path, 0o600, nil)
return nil if err != nil {
}, t.Fatalf("failed to open bolt database: %v", err)
} }
svc := reqlog.NewService(reqlog.Config{ defer boltDB.Close()
Repository: repoMock,
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
}) })
svc.SetActiveProjectID(ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)) if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
svc := reqlog.NewService(reqlog.Config{
Repository: db,
})
svc.SetActiveProjectID(projectID)
next := func(res *http.Response) error { next := func(res *http.Response) error {
res.Body = io.NopCloser(strings.NewReader("modified body")) res.Body = io.NopCloser(strings.NewReader("modified body"))
@ -92,6 +123,14 @@ func TestResponseModifier(t *testing.T) {
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID)) req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID))
err = db.StoreRequestLog(context.Background(), reqlog.RequestLog{
ID: reqLogID,
ProjectID: projectID,
})
if err != nil {
t.Fatalf("failed to store request log: %v", err)
}
res := &http.Response{ res := &http.Response{
Request: req, Request: req,
Body: io.NopCloser(strings.NewReader("bar")), Body: io.NopCloser(strings.NewReader("bar")),
@ -104,23 +143,15 @@ func TestResponseModifier(t *testing.T) {
t.Run("request log was stored in repository", func(t *testing.T) { t.Run("request log was stored in repository", func(t *testing.T) {
// Dirty (but simple) wait for other goroutine to finish calling repository. // Dirty (but simple) wait for other goroutine to finish calling repository.
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
got := len(repoMock.StoreResponseLogCalls())
if exp := 1; exp != got { got, err := svc.FindRequestLogByID(context.Background(), reqLogID)
t.Fatalf("incorrect `proj.Service.AddResponseLog` calls (expected: %v, got: %v)", exp, got) if err != nil {
t.Fatalf("failed to find request by id: %v", err)
} }
t.Run("ran next modifier first, before calling repository", func(t *testing.T) { t.Run("ran next modifier first, before calling repository", func(t *testing.T) {
got := repoMock.StoreResponseLogCalls()[0].ResLog.Body if exp := "modified body"; exp != string(got.Response.Body) {
if exp := "modified body"; exp != string(got) { t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got.Response.Body))
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got))
}
})
t.Run("called repository with request log id", func(t *testing.T) {
got := repoMock.StoreResponseLogCalls()[0].ReqLogID
if exp := reqLogID; exp.Compare(got) != 0 {
t.Fatalf("incorrect `reqLogID` argument for `Repository.AddResponseLogCalls` (expected: %v, got: %v)",
exp.String(), got.String())
} }
}) })
}) })

View File

@ -5,14 +5,12 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
) )
type Repository interface { type Repository interface {
FindSenderRequestByID(ctx context.Context, id ulid.ULID) (Request, error) FindSenderRequestByID(ctx context.Context, projectID, id ulid.ULID) (Request, error)
FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error) FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
StoreSenderRequest(ctx context.Context, req Request) error StoreSenderRequest(ctx context.Context, req Request) error
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error
} }

View File

@ -1,292 +0,0 @@
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package sender_test
import (
"context"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/sender"
"github.com/oklog/ulid"
"sync"
)
// Ensure, that RepoMock does implement sender.Repository.
// If this is not the case, regenerate this file with moq.
var _ sender.Repository = &RepoMock{}
// RepoMock is a mock implementation of sender.Repository.
//
// func TestSomethingThatUsesRepository(t *testing.T) {
//
// // make and configure a mocked sender.Repository
// mockedRepository := &RepoMock{
// DeleteSenderRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
// panic("mock out the DeleteSenderRequests method")
// },
// FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) {
// panic("mock out the FindSenderRequestByID method")
// },
// FindSenderRequestsFunc: func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
// panic("mock out the FindSenderRequests method")
// },
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
// panic("mock out the StoreResponseLog method")
// },
// StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
// panic("mock out the StoreSenderRequest method")
// },
// }
//
// // use mockedRepository in code that requires sender.Repository
// // and then make assertions.
//
// }
type RepoMock struct {
// DeleteSenderRequestsFunc mocks the DeleteSenderRequests method.
DeleteSenderRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
// FindSenderRequestByIDFunc mocks the FindSenderRequestByID method.
FindSenderRequestByIDFunc func(ctx context.Context, id ulid.ULID) (sender.Request, error)
// FindSenderRequestsFunc mocks the FindSenderRequests method.
FindSenderRequestsFunc func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error)
// StoreResponseLogFunc mocks the StoreResponseLog method.
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
// StoreSenderRequestFunc mocks the StoreSenderRequest method.
StoreSenderRequestFunc func(ctx context.Context, req sender.Request) error
// calls tracks calls to the methods.
calls struct {
// DeleteSenderRequests holds details about calls to the DeleteSenderRequests method.
DeleteSenderRequests []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ProjectID is the projectID argument value.
ProjectID ulid.ULID
}
// FindSenderRequestByID holds details about calls to the FindSenderRequestByID method.
FindSenderRequestByID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ID is the id argument value.
ID ulid.ULID
}
// FindSenderRequests holds details about calls to the FindSenderRequests method.
FindSenderRequests []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Filter is the filter argument value.
Filter sender.FindRequestsFilter
// ScopeMoqParam is the scopeMoqParam argument value.
ScopeMoqParam *scope.Scope
}
// StoreResponseLog holds details about calls to the StoreResponseLog method.
StoreResponseLog []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ReqLogID is the reqLogID argument value.
ReqLogID ulid.ULID
// ResLog is the resLog argument value.
ResLog reqlog.ResponseLog
}
// StoreSenderRequest holds details about calls to the StoreSenderRequest method.
StoreSenderRequest []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Req is the req argument value.
Req sender.Request
}
}
lockDeleteSenderRequests sync.RWMutex
lockFindSenderRequestByID sync.RWMutex
lockFindSenderRequests sync.RWMutex
lockStoreResponseLog sync.RWMutex
lockStoreSenderRequest sync.RWMutex
}
// DeleteSenderRequests calls DeleteSenderRequestsFunc.
func (mock *RepoMock) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
if mock.DeleteSenderRequestsFunc == nil {
panic("RepoMock.DeleteSenderRequestsFunc: method is nil but Repository.DeleteSenderRequests was just called")
}
callInfo := struct {
Ctx context.Context
ProjectID ulid.ULID
}{
Ctx: ctx,
ProjectID: projectID,
}
mock.lockDeleteSenderRequests.Lock()
mock.calls.DeleteSenderRequests = append(mock.calls.DeleteSenderRequests, callInfo)
mock.lockDeleteSenderRequests.Unlock()
return mock.DeleteSenderRequestsFunc(ctx, projectID)
}
// DeleteSenderRequestsCalls gets all the calls that were made to DeleteSenderRequests.
// Check the length with:
// len(mockedRepository.DeleteSenderRequestsCalls())
func (mock *RepoMock) DeleteSenderRequestsCalls() []struct {
Ctx context.Context
ProjectID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ProjectID ulid.ULID
}
mock.lockDeleteSenderRequests.RLock()
calls = mock.calls.DeleteSenderRequests
mock.lockDeleteSenderRequests.RUnlock()
return calls
}
// FindSenderRequestByID calls FindSenderRequestByIDFunc.
func (mock *RepoMock) FindSenderRequestByID(ctx context.Context, id ulid.ULID) (sender.Request, error) {
if mock.FindSenderRequestByIDFunc == nil {
panic("RepoMock.FindSenderRequestByIDFunc: method is nil but Repository.FindSenderRequestByID was just called")
}
callInfo := struct {
Ctx context.Context
ID ulid.ULID
}{
Ctx: ctx,
ID: id,
}
mock.lockFindSenderRequestByID.Lock()
mock.calls.FindSenderRequestByID = append(mock.calls.FindSenderRequestByID, callInfo)
mock.lockFindSenderRequestByID.Unlock()
return mock.FindSenderRequestByIDFunc(ctx, id)
}
// FindSenderRequestByIDCalls gets all the calls that were made to FindSenderRequestByID.
// Check the length with:
// len(mockedRepository.FindSenderRequestByIDCalls())
func (mock *RepoMock) FindSenderRequestByIDCalls() []struct {
Ctx context.Context
ID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ID ulid.ULID
}
mock.lockFindSenderRequestByID.RLock()
calls = mock.calls.FindSenderRequestByID
mock.lockFindSenderRequestByID.RUnlock()
return calls
}
// FindSenderRequests calls FindSenderRequestsFunc.
func (mock *RepoMock) FindSenderRequests(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
if mock.FindSenderRequestsFunc == nil {
panic("RepoMock.FindSenderRequestsFunc: method is nil but Repository.FindSenderRequests was just called")
}
callInfo := struct {
Ctx context.Context
Filter sender.FindRequestsFilter
ScopeMoqParam *scope.Scope
}{
Ctx: ctx,
Filter: filter,
ScopeMoqParam: scopeMoqParam,
}
mock.lockFindSenderRequests.Lock()
mock.calls.FindSenderRequests = append(mock.calls.FindSenderRequests, callInfo)
mock.lockFindSenderRequests.Unlock()
return mock.FindSenderRequestsFunc(ctx, filter, scopeMoqParam)
}
// FindSenderRequestsCalls gets all the calls that were made to FindSenderRequests.
// Check the length with:
// len(mockedRepository.FindSenderRequestsCalls())
func (mock *RepoMock) FindSenderRequestsCalls() []struct {
Ctx context.Context
Filter sender.FindRequestsFilter
ScopeMoqParam *scope.Scope
} {
var calls []struct {
Ctx context.Context
Filter sender.FindRequestsFilter
ScopeMoqParam *scope.Scope
}
mock.lockFindSenderRequests.RLock()
calls = mock.calls.FindSenderRequests
mock.lockFindSenderRequests.RUnlock()
return calls
}
// StoreResponseLog calls StoreResponseLogFunc.
func (mock *RepoMock) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
if mock.StoreResponseLogFunc == nil {
panic("RepoMock.StoreResponseLogFunc: method is nil but Repository.StoreResponseLog was just called")
}
callInfo := struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
}{
Ctx: ctx,
ReqLogID: reqLogID,
ResLog: resLog,
}
mock.lockStoreResponseLog.Lock()
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
mock.lockStoreResponseLog.Unlock()
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
}
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
// Check the length with:
// len(mockedRepository.StoreResponseLogCalls())
func (mock *RepoMock) StoreResponseLogCalls() []struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
} {
var calls []struct {
Ctx context.Context
ReqLogID ulid.ULID
ResLog reqlog.ResponseLog
}
mock.lockStoreResponseLog.RLock()
calls = mock.calls.StoreResponseLog
mock.lockStoreResponseLog.RUnlock()
return calls
}
// StoreSenderRequest calls StoreSenderRequestFunc.
func (mock *RepoMock) StoreSenderRequest(ctx context.Context, req sender.Request) error {
if mock.StoreSenderRequestFunc == nil {
panic("RepoMock.StoreSenderRequestFunc: method is nil but Repository.StoreSenderRequest was just called")
}
callInfo := struct {
Ctx context.Context
Req sender.Request
}{
Ctx: ctx,
Req: req,
}
mock.lockStoreSenderRequest.Lock()
mock.calls.StoreSenderRequest = append(mock.calls.StoreSenderRequest, callInfo)
mock.lockStoreSenderRequest.Unlock()
return mock.StoreSenderRequestFunc(ctx, req)
}
// StoreSenderRequestCalls gets all the calls that were made to StoreSenderRequest.
// Check the length with:
// len(mockedRepository.StoreSenderRequestCalls())
func (mock *RepoMock) StoreSenderRequestCalls() []struct {
Ctx context.Context
Req sender.Request
} {
var calls []struct {
Ctx context.Context
Req sender.Request
}
mock.lockStoreSenderRequest.RLock()
calls = mock.calls.StoreSenderRequest
mock.lockStoreSenderRequest.RUnlock()
return calls
}

View File

@ -1,498 +0,0 @@
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package sender_test
import (
"context"
"github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/oklog/ulid"
"sync"
)
// Ensure, that ReqLogServiceMock does implement reqlog.Service.
// If this is not the case, regenerate this file with moq.
var _ reqlog.Service = &ReqLogServiceMock{}
// ReqLogServiceMock is a mock implementation of reqlog.Service.
//
// func TestSomethingThatUsesService(t *testing.T) {
//
// // make and configure a mocked reqlog.Service
// mockedService := &ReqLogServiceMock{
// ActiveProjectIDFunc: func() ulid.ULID {
// panic("mock out the ActiveProjectID method")
// },
// BypassOutOfScopeRequestsFunc: func() bool {
// panic("mock out the BypassOutOfScopeRequests method")
// },
// ClearRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
// panic("mock out the ClearRequests method")
// },
// FindReqsFilterFunc: func() reqlog.FindRequestsFilter {
// panic("mock out the FindReqsFilter method")
// },
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
// panic("mock out the FindRequestLogByID method")
// },
// FindRequestsFunc: func(ctx context.Context) ([]reqlog.RequestLog, error) {
// panic("mock out the FindRequests method")
// },
// RequestModifierFunc: func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
// panic("mock out the RequestModifier method")
// },
// ResponseModifierFunc: func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
// panic("mock out the ResponseModifier method")
// },
// SetActiveProjectIDFunc: func(id ulid.ULID) {
// panic("mock out the SetActiveProjectID method")
// },
// SetBypassOutOfScopeRequestsFunc: func(b bool) {
// panic("mock out the SetBypassOutOfScopeRequests method")
// },
// SetFindReqsFilterFunc: func(filter reqlog.FindRequestsFilter) {
// panic("mock out the SetFindReqsFilter method")
// },
// }
//
// // use mockedService in code that requires reqlog.Service
// // and then make assertions.
//
// }
type ReqLogServiceMock struct {
// ActiveProjectIDFunc mocks the ActiveProjectID method.
ActiveProjectIDFunc func() ulid.ULID
// BypassOutOfScopeRequestsFunc mocks the BypassOutOfScopeRequests method.
BypassOutOfScopeRequestsFunc func() bool
// ClearRequestsFunc mocks the ClearRequests method.
ClearRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
// FindReqsFilterFunc mocks the FindReqsFilter method.
FindReqsFilterFunc func() reqlog.FindRequestsFilter
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
// FindRequestsFunc mocks the FindRequests method.
FindRequestsFunc func(ctx context.Context) ([]reqlog.RequestLog, error)
// RequestModifierFunc mocks the RequestModifier method.
RequestModifierFunc func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc
// ResponseModifierFunc mocks the ResponseModifier method.
ResponseModifierFunc func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc
// SetActiveProjectIDFunc mocks the SetActiveProjectID method.
SetActiveProjectIDFunc func(id ulid.ULID)
// SetBypassOutOfScopeRequestsFunc mocks the SetBypassOutOfScopeRequests method.
SetBypassOutOfScopeRequestsFunc func(b bool)
// SetFindReqsFilterFunc mocks the SetFindReqsFilter method.
SetFindReqsFilterFunc func(filter reqlog.FindRequestsFilter)
// calls tracks calls to the methods.
calls struct {
// ActiveProjectID holds details about calls to the ActiveProjectID method.
ActiveProjectID []struct {
}
// BypassOutOfScopeRequests holds details about calls to the BypassOutOfScopeRequests method.
BypassOutOfScopeRequests []struct {
}
// ClearRequests holds details about calls to the ClearRequests method.
ClearRequests []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ProjectID is the projectID argument value.
ProjectID ulid.ULID
}
// FindReqsFilter holds details about calls to the FindReqsFilter method.
FindReqsFilter []struct {
}
// FindRequestLogByID holds details about calls to the FindRequestLogByID method.
FindRequestLogByID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// ID is the id argument value.
ID ulid.ULID
}
// FindRequests holds details about calls to the FindRequests method.
FindRequests []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// RequestModifier holds details about calls to the RequestModifier method.
RequestModifier []struct {
// Next is the next argument value.
Next proxy.RequestModifyFunc
}
// ResponseModifier holds details about calls to the ResponseModifier method.
ResponseModifier []struct {
// Next is the next argument value.
Next proxy.ResponseModifyFunc
}
// SetActiveProjectID holds details about calls to the SetActiveProjectID method.
SetActiveProjectID []struct {
// ID is the id argument value.
ID ulid.ULID
}
// SetBypassOutOfScopeRequests holds details about calls to the SetBypassOutOfScopeRequests method.
SetBypassOutOfScopeRequests []struct {
// B is the b argument value.
B bool
}
// SetFindReqsFilter holds details about calls to the SetFindReqsFilter method.
SetFindReqsFilter []struct {
// Filter is the filter argument value.
Filter reqlog.FindRequestsFilter
}
}
lockActiveProjectID sync.RWMutex
lockBypassOutOfScopeRequests sync.RWMutex
lockClearRequests sync.RWMutex
lockFindReqsFilter sync.RWMutex
lockFindRequestLogByID sync.RWMutex
lockFindRequests sync.RWMutex
lockRequestModifier sync.RWMutex
lockResponseModifier sync.RWMutex
lockSetActiveProjectID sync.RWMutex
lockSetBypassOutOfScopeRequests sync.RWMutex
lockSetFindReqsFilter sync.RWMutex
}
// ActiveProjectID calls ActiveProjectIDFunc.
func (mock *ReqLogServiceMock) ActiveProjectID() ulid.ULID {
if mock.ActiveProjectIDFunc == nil {
panic("ReqLogServiceMock.ActiveProjectIDFunc: method is nil but Service.ActiveProjectID was just called")
}
callInfo := struct {
}{}
mock.lockActiveProjectID.Lock()
mock.calls.ActiveProjectID = append(mock.calls.ActiveProjectID, callInfo)
mock.lockActiveProjectID.Unlock()
return mock.ActiveProjectIDFunc()
}
// ActiveProjectIDCalls gets all the calls that were made to ActiveProjectID.
// Check the length with:
// len(mockedService.ActiveProjectIDCalls())
func (mock *ReqLogServiceMock) ActiveProjectIDCalls() []struct {
} {
var calls []struct {
}
mock.lockActiveProjectID.RLock()
calls = mock.calls.ActiveProjectID
mock.lockActiveProjectID.RUnlock()
return calls
}
// BypassOutOfScopeRequests calls BypassOutOfScopeRequestsFunc.
func (mock *ReqLogServiceMock) BypassOutOfScopeRequests() bool {
if mock.BypassOutOfScopeRequestsFunc == nil {
panic("ReqLogServiceMock.BypassOutOfScopeRequestsFunc: method is nil but Service.BypassOutOfScopeRequests was just called")
}
callInfo := struct {
}{}
mock.lockBypassOutOfScopeRequests.Lock()
mock.calls.BypassOutOfScopeRequests = append(mock.calls.BypassOutOfScopeRequests, callInfo)
mock.lockBypassOutOfScopeRequests.Unlock()
return mock.BypassOutOfScopeRequestsFunc()
}
// BypassOutOfScopeRequestsCalls gets all the calls that were made to BypassOutOfScopeRequests.
// Check the length with:
// len(mockedService.BypassOutOfScopeRequestsCalls())
func (mock *ReqLogServiceMock) BypassOutOfScopeRequestsCalls() []struct {
} {
var calls []struct {
}
mock.lockBypassOutOfScopeRequests.RLock()
calls = mock.calls.BypassOutOfScopeRequests
mock.lockBypassOutOfScopeRequests.RUnlock()
return calls
}
// ClearRequests calls ClearRequestsFunc.
func (mock *ReqLogServiceMock) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
if mock.ClearRequestsFunc == nil {
panic("ReqLogServiceMock.ClearRequestsFunc: method is nil but Service.ClearRequests was just called")
}
callInfo := struct {
Ctx context.Context
ProjectID ulid.ULID
}{
Ctx: ctx,
ProjectID: projectID,
}
mock.lockClearRequests.Lock()
mock.calls.ClearRequests = append(mock.calls.ClearRequests, callInfo)
mock.lockClearRequests.Unlock()
return mock.ClearRequestsFunc(ctx, projectID)
}
// ClearRequestsCalls gets all the calls that were made to ClearRequests.
// Check the length with:
// len(mockedService.ClearRequestsCalls())
func (mock *ReqLogServiceMock) ClearRequestsCalls() []struct {
Ctx context.Context
ProjectID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ProjectID ulid.ULID
}
mock.lockClearRequests.RLock()
calls = mock.calls.ClearRequests
mock.lockClearRequests.RUnlock()
return calls
}
// FindReqsFilter calls FindReqsFilterFunc.
func (mock *ReqLogServiceMock) FindReqsFilter() reqlog.FindRequestsFilter {
if mock.FindReqsFilterFunc == nil {
panic("ReqLogServiceMock.FindReqsFilterFunc: method is nil but Service.FindReqsFilter was just called")
}
callInfo := struct {
}{}
mock.lockFindReqsFilter.Lock()
mock.calls.FindReqsFilter = append(mock.calls.FindReqsFilter, callInfo)
mock.lockFindReqsFilter.Unlock()
return mock.FindReqsFilterFunc()
}
// FindReqsFilterCalls gets all the calls that were made to FindReqsFilter.
// Check the length with:
// len(mockedService.FindReqsFilterCalls())
func (mock *ReqLogServiceMock) FindReqsFilterCalls() []struct {
} {
var calls []struct {
}
mock.lockFindReqsFilter.RLock()
calls = mock.calls.FindReqsFilter
mock.lockFindReqsFilter.RUnlock()
return calls
}
// FindRequestLogByID calls FindRequestLogByIDFunc.
func (mock *ReqLogServiceMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
if mock.FindRequestLogByIDFunc == nil {
panic("ReqLogServiceMock.FindRequestLogByIDFunc: method is nil but Service.FindRequestLogByID was just called")
}
callInfo := struct {
Ctx context.Context
ID ulid.ULID
}{
Ctx: ctx,
ID: id,
}
mock.lockFindRequestLogByID.Lock()
mock.calls.FindRequestLogByID = append(mock.calls.FindRequestLogByID, callInfo)
mock.lockFindRequestLogByID.Unlock()
return mock.FindRequestLogByIDFunc(ctx, id)
}
// FindRequestLogByIDCalls gets all the calls that were made to FindRequestLogByID.
// Check the length with:
// len(mockedService.FindRequestLogByIDCalls())
func (mock *ReqLogServiceMock) FindRequestLogByIDCalls() []struct {
Ctx context.Context
ID ulid.ULID
} {
var calls []struct {
Ctx context.Context
ID ulid.ULID
}
mock.lockFindRequestLogByID.RLock()
calls = mock.calls.FindRequestLogByID
mock.lockFindRequestLogByID.RUnlock()
return calls
}
// FindRequests calls FindRequestsFunc.
func (mock *ReqLogServiceMock) FindRequests(ctx context.Context) ([]reqlog.RequestLog, error) {
if mock.FindRequestsFunc == nil {
panic("ReqLogServiceMock.FindRequestsFunc: method is nil but Service.FindRequests was just called")
}
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockFindRequests.Lock()
mock.calls.FindRequests = append(mock.calls.FindRequests, callInfo)
mock.lockFindRequests.Unlock()
return mock.FindRequestsFunc(ctx)
}
// FindRequestsCalls gets all the calls that were made to FindRequests.
// Check the length with:
// len(mockedService.FindRequestsCalls())
func (mock *ReqLogServiceMock) FindRequestsCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockFindRequests.RLock()
calls = mock.calls.FindRequests
mock.lockFindRequests.RUnlock()
return calls
}
// RequestModifier calls RequestModifierFunc.
func (mock *ReqLogServiceMock) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
if mock.RequestModifierFunc == nil {
panic("ReqLogServiceMock.RequestModifierFunc: method is nil but Service.RequestModifier was just called")
}
callInfo := struct {
Next proxy.RequestModifyFunc
}{
Next: next,
}
mock.lockRequestModifier.Lock()
mock.calls.RequestModifier = append(mock.calls.RequestModifier, callInfo)
mock.lockRequestModifier.Unlock()
return mock.RequestModifierFunc(next)
}
// RequestModifierCalls gets all the calls that were made to RequestModifier.
// Check the length with:
// len(mockedService.RequestModifierCalls())
func (mock *ReqLogServiceMock) RequestModifierCalls() []struct {
Next proxy.RequestModifyFunc
} {
var calls []struct {
Next proxy.RequestModifyFunc
}
mock.lockRequestModifier.RLock()
calls = mock.calls.RequestModifier
mock.lockRequestModifier.RUnlock()
return calls
}
// ResponseModifier calls ResponseModifierFunc.
func (mock *ReqLogServiceMock) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
if mock.ResponseModifierFunc == nil {
panic("ReqLogServiceMock.ResponseModifierFunc: method is nil but Service.ResponseModifier was just called")
}
callInfo := struct {
Next proxy.ResponseModifyFunc
}{
Next: next,
}
mock.lockResponseModifier.Lock()
mock.calls.ResponseModifier = append(mock.calls.ResponseModifier, callInfo)
mock.lockResponseModifier.Unlock()
return mock.ResponseModifierFunc(next)
}
// ResponseModifierCalls gets all the calls that were made to ResponseModifier.
// Check the length with:
// len(mockedService.ResponseModifierCalls())
func (mock *ReqLogServiceMock) ResponseModifierCalls() []struct {
Next proxy.ResponseModifyFunc
} {
var calls []struct {
Next proxy.ResponseModifyFunc
}
mock.lockResponseModifier.RLock()
calls = mock.calls.ResponseModifier
mock.lockResponseModifier.RUnlock()
return calls
}
// SetActiveProjectID calls SetActiveProjectIDFunc.
func (mock *ReqLogServiceMock) SetActiveProjectID(id ulid.ULID) {
if mock.SetActiveProjectIDFunc == nil {
panic("ReqLogServiceMock.SetActiveProjectIDFunc: method is nil but Service.SetActiveProjectID was just called")
}
callInfo := struct {
ID ulid.ULID
}{
ID: id,
}
mock.lockSetActiveProjectID.Lock()
mock.calls.SetActiveProjectID = append(mock.calls.SetActiveProjectID, callInfo)
mock.lockSetActiveProjectID.Unlock()
mock.SetActiveProjectIDFunc(id)
}
// SetActiveProjectIDCalls gets all the calls that were made to SetActiveProjectID.
// Check the length with:
// len(mockedService.SetActiveProjectIDCalls())
func (mock *ReqLogServiceMock) SetActiveProjectIDCalls() []struct {
ID ulid.ULID
} {
var calls []struct {
ID ulid.ULID
}
mock.lockSetActiveProjectID.RLock()
calls = mock.calls.SetActiveProjectID
mock.lockSetActiveProjectID.RUnlock()
return calls
}
// SetBypassOutOfScopeRequests calls SetBypassOutOfScopeRequestsFunc.
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequests(b bool) {
if mock.SetBypassOutOfScopeRequestsFunc == nil {
panic("ReqLogServiceMock.SetBypassOutOfScopeRequestsFunc: method is nil but Service.SetBypassOutOfScopeRequests was just called")
}
callInfo := struct {
B bool
}{
B: b,
}
mock.lockSetBypassOutOfScopeRequests.Lock()
mock.calls.SetBypassOutOfScopeRequests = append(mock.calls.SetBypassOutOfScopeRequests, callInfo)
mock.lockSetBypassOutOfScopeRequests.Unlock()
mock.SetBypassOutOfScopeRequestsFunc(b)
}
// SetBypassOutOfScopeRequestsCalls gets all the calls that were made to SetBypassOutOfScopeRequests.
// Check the length with:
// len(mockedService.SetBypassOutOfScopeRequestsCalls())
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequestsCalls() []struct {
B bool
} {
var calls []struct {
B bool
}
mock.lockSetBypassOutOfScopeRequests.RLock()
calls = mock.calls.SetBypassOutOfScopeRequests
mock.lockSetBypassOutOfScopeRequests.RUnlock()
return calls
}
// SetFindReqsFilter calls SetFindReqsFilterFunc.
func (mock *ReqLogServiceMock) SetFindReqsFilter(filter reqlog.FindRequestsFilter) {
if mock.SetFindReqsFilterFunc == nil {
panic("ReqLogServiceMock.SetFindReqsFilterFunc: method is nil but Service.SetFindReqsFilter was just called")
}
callInfo := struct {
Filter reqlog.FindRequestsFilter
}{
Filter: filter,
}
mock.lockSetFindReqsFilter.Lock()
mock.calls.SetFindReqsFilter = append(mock.calls.SetFindReqsFilter, callInfo)
mock.lockSetFindReqsFilter.Unlock()
mock.SetFindReqsFilterFunc(filter)
}
// SetFindReqsFilterCalls gets all the calls that were made to SetFindReqsFilter.
// Check the length with:
// len(mockedService.SetFindReqsFilterCalls())
func (mock *ReqLogServiceMock) SetFindReqsFilterCalls() []struct {
Filter reqlog.FindRequestsFilter
} {
var calls []struct {
Filter reqlog.FindRequestsFilter
}
mock.lockSetFindReqsFilter.RLock()
calls = mock.calls.SetFindReqsFilter
mock.lockSetFindReqsFilter.RUnlock()
return calls
}

View File

@ -30,24 +30,12 @@ var (
ErrRequestNotFound = errors.New("sender: request not found") ErrRequestNotFound = errors.New("sender: request not found")
) )
type Service interface { type Service struct {
FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error)
FindRequests(ctx context.Context) ([]Request, error)
CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error)
CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error)
DeleteRequests(ctx context.Context, projectID ulid.ULID) error
SendRequest(ctx context.Context, id ulid.ULID) (Request, error)
SetActiveProjectID(ulid.ULID)
SetFindReqsFilter(filter FindRequestsFilter)
FindReqsFilter() FindRequestsFilter
}
type service struct {
activeProjectID ulid.ULID activeProjectID ulid.ULID
findReqsFilter FindRequestsFilter findReqsFilter FindRequestsFilter
scope *scope.Scope scope *scope.Scope
repo Repository repo Repository
reqLogSvc reqlog.Service reqLogSvc *reqlog.Service
httpClient *http.Client httpClient *http.Client
} }
@ -60,7 +48,7 @@ type FindRequestsFilter struct {
type Config struct { type Config struct {
Scope *scope.Scope Scope *scope.Scope
Repository Repository Repository Repository
ReqLogService reqlog.Service ReqLogService *reqlog.Service
HTTPClient *http.Client HTTPClient *http.Client
} }
@ -68,8 +56,8 @@ type SendError struct {
err error err error
} }
func NewService(cfg Config) Service { func NewService(cfg Config) *Service {
svc := &service{ svc := &Service{
repo: cfg.Repository, repo: cfg.Repository,
reqLogSvc: cfg.ReqLogService, reqLogSvc: cfg.ReqLogService,
httpClient: defaultHTTPClient, httpClient: defaultHTTPClient,
@ -97,8 +85,8 @@ type Request struct {
Response *reqlog.ResponseLog Response *reqlog.ResponseLog
} }
func (svc *service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) { func (svc *Service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) {
req, err := svc.repo.FindSenderRequestByID(ctx, id) req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
if err != nil { if err != nil {
return Request{}, fmt.Errorf("sender: failed to find request: %w", err) return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
} }
@ -106,11 +94,11 @@ func (svc *service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request,
return req, nil return req, nil
} }
func (svc *service) FindRequests(ctx context.Context) ([]Request, error) { func (svc *Service) FindRequests(ctx context.Context) ([]Request, error) {
return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope) return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope)
} }
func (svc *service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) { func (svc *Service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) {
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 { if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
return Request{}, ErrProjectIDMustBeSet return Request{}, ErrProjectIDMustBeSet
} }
@ -141,7 +129,7 @@ func (svc *service) CreateOrUpdateRequest(ctx context.Context, req Request) (Req
return req, nil return req, nil
} }
func (svc *service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) { func (svc *Service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) {
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 { if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
return Request{}, ErrProjectIDMustBeSet return Request{}, ErrProjectIDMustBeSet
} }
@ -170,16 +158,16 @@ func (svc *service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID)
return req, nil return req, nil
} }
func (svc *service) SetFindReqsFilter(filter FindRequestsFilter) { func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
svc.findReqsFilter = filter svc.findReqsFilter = filter
} }
func (svc *service) FindReqsFilter() FindRequestsFilter { func (svc *Service) FindReqsFilter() FindRequestsFilter {
return svc.findReqsFilter return svc.findReqsFilter
} }
func (svc *service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) { func (svc *Service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) {
req, err := svc.repo.FindSenderRequestByID(ctx, id) req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
if err != nil { if err != nil {
return Request{}, fmt.Errorf("sender: failed to find request: %w", err) return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
} }
@ -194,7 +182,9 @@ func (svc *service) SendRequest(ctx context.Context, id ulid.ULID) (Request, err
return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err) return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err)
} }
err = svc.repo.StoreResponseLog(ctx, id, resLog) req.Response = &resLog
err = svc.repo.StoreSenderRequest(ctx, req)
if err != nil { if err != nil {
return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err) return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err)
} }
@ -219,7 +209,7 @@ func parseHTTPRequest(ctx context.Context, req Request) (*http.Request, error) {
return httpReq, nil return httpReq, nil
} }
func (svc *service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) { func (svc *Service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) {
res, err := svc.httpClient.Do(httpReq) res, err := svc.httpClient.Do(httpReq)
if err != nil { if err != nil {
return reqlog.ResponseLog{}, &SendError{err} return reqlog.ResponseLog{}, &SendError{err}
@ -234,11 +224,11 @@ func (svc *service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog,
return resLog, err return resLog, err
} }
func (svc *service) SetActiveProjectID(id ulid.ULID) { func (svc *Service) SetActiveProjectID(id ulid.ULID) {
svc.activeProjectID = id svc.activeProjectID = id
} }
func (svc *service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error { func (svc *Service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error {
return svc.repo.DeleteSenderRequests(ctx, projectID) return svc.repo.DeleteSenderRequests(ctx, projectID)
} }

View File

@ -1,8 +1,5 @@
package sender_test package sender_test
//go:generate go run github.com/matryer/moq -out reqlog_mock_test.go -pkg sender_test ../reqlog Service:ReqLogServiceMock
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg sender_test . Repository:RepoMock
import ( import (
"context" "context"
"errors" "errors"
@ -15,8 +12,12 @@ import (
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"go.etcd.io/bbolt"
"github.com/dstotijn/hetty/pkg/db/bolt"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/sender" "github.com/dstotijn/hetty/pkg/sender"
) )
@ -54,16 +55,34 @@ func TestStoreRequest(t *testing.T) {
t.Run("with active project", func(t *testing.T) { t.Run("with active project", func(t *testing.T) {
t.Parallel() t.Parallel()
repoMock := &RepoMock{ path := t.TempDir() + "bolt.db"
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error { boltDB, err := bbolt.Open(path, 0o600, nil)
return nil if err != nil {
}, t.Fatalf("failed to open bolt database: %v", err)
} }
defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
svc := sender.NewService(sender.Config{ svc := sender.NewService(sender.Config{
Repository: repoMock, Repository: db,
}) })
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
Name: "foobar",
Settings: proj.Settings{},
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
svc.SetActiveProjectID(projectID)
svc.SetActiveProjectID(projectID) svc.SetActiveProjectID(projectID)
exp := sender.Request{ exp := sender.Request{
@ -94,18 +113,18 @@ func TestStoreRequest(t *testing.T) {
t.Fatal("expected request ID to be non-empty value") t.Fatal("expected request ID to be non-empty value")
} }
if len(repoMock.StoreSenderRequestCalls()) != 1 { diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time") if diff != "" {
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
} }
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" { got, err = db.FindSenderRequestByID(context.Background(), projectID, got.ID)
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff) if err != nil {
t.Fatalf("failed to find request by ID: %v", err)
} }
// Reset ID to make comparison with expected easier. diff = cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
got.ID = ulid.ULID{} if diff != "" {
if diff := cmp.Diff(exp, got); diff != "" {
t.Fatalf("request not equal (-exp, +got):\n%v", diff) t.Fatalf("request not equal (-exp, +got):\n%v", diff)
} }
}) })
@ -130,9 +149,30 @@ func TestCloneFromRequestLog(t *testing.T) {
t.Run("with active project", func(t *testing.T) { t.Run("with active project", func(t *testing.T) {
t.Parallel() t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
reqLog := reqlog.RequestLog{ reqLog := reqlog.RequestLog{
ID: reqLogID, ID: reqLogID,
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ProjectID: projectID,
URL: exampleURL, URL: exampleURL,
Method: http.MethodPost, Method: http.MethodPost,
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
@ -142,22 +182,18 @@ func TestCloneFromRequestLog(t *testing.T) {
Body: []byte("foobar"), Body: []byte("foobar"),
} }
reqLogMock := &ReqLogServiceMock{ if err := db.StoreRequestLog(context.Background(), reqLog); err != nil {
FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) { t.Fatalf("failed to store request log: %v", err)
return reqLog, nil
},
}
repoMock := &RepoMock{
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
return nil
},
} }
svc := sender.NewService(sender.Config{ svc := sender.NewService(sender.Config{
ReqLogService: reqLogMock, ReqLogService: reqlog.NewService(reqlog.Config{
Repository: repoMock, ActiveProjectID: projectID,
Repository: db,
}),
Repository: db,
}) })
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
svc.SetActiveProjectID(projectID) svc.SetActiveProjectID(projectID)
exp := sender.Request{ exp := sender.Request{
@ -177,30 +213,8 @@ func TestCloneFromRequestLog(t *testing.T) {
t.Fatalf("unexpected error cloning from request log: %v", err) t.Fatalf("unexpected error cloning from request log: %v", err)
} }
if len(reqLogMock.FindRequestLogByIDCalls()) != 1 { diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
t.Fatal("expected `svc.reqLogSvc.FindRequestLogByID()` to have been called 1 time") if diff != "" {
}
if got := reqLogMock.FindRequestLogByIDCalls()[0].ID; reqLogID.Compare(got) != 0 {
t.Fatalf("reqlog service call arg `id` not equal (expected: %q, got: %q)", reqLogID, got)
}
if got.ID.Compare(ulid.ULID{}) == 0 {
t.Fatal("expected request ID to be non-empty value")
}
if len(repoMock.StoreSenderRequestCalls()) != 1 {
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time")
}
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" {
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff)
}
// Reset ID to make comparison with expected easier.
got.ID = ulid.ULID{}
if diff := cmp.Diff(exp, got); diff != "" {
t.Fatalf("request not equal (-exp, +got):\n%v", diff) t.Fatalf("request not equal (-exp, +got):\n%v", diff)
} }
}) })
@ -209,6 +223,19 @@ func TestCloneFromRequestLog(t *testing.T) {
func TestSendRequest(t *testing.T) { func TestSendRequest(t *testing.T) {
t.Parallel() t.Parallel()
path := t.TempDir() + "bolt.db"
boltDB, err := bbolt.Open(path, 0o600, nil)
if err != nil {
t.Fatalf("failed to open bolt database: %v", err)
}
defer boltDB.Close()
db, err := bolt.DatabaseFromBoltDB(boltDB)
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer db.Close()
date := time.Now().Format(http.TimeFormat) date := time.Now().Format(http.TimeFormat)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -220,10 +247,19 @@ func TestSendRequest(t *testing.T) {
tsURL, _ := url.Parse(ts.URL) tsURL, _ := url.Parse(ts.URL)
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err = db.UpsertProject(context.Background(), proj.Project{
ID: projectID,
Settings: proj.Settings{},
})
if err != nil {
t.Fatalf("unexpected error upserting project: %v", err)
}
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
req := sender.Request{ req := sender.Request{
ID: reqID, ID: reqID,
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy), ProjectID: projectID,
URL: tsURL, URL: tsURL,
Method: http.MethodPost, Method: http.MethodPost,
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
@ -233,19 +269,19 @@ func TestSendRequest(t *testing.T) {
Body: []byte("foobar"), Body: []byte("foobar"),
} }
repoMock := &RepoMock{ if err := db.StoreSenderRequest(context.Background(), req); err != nil {
FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) { t.Fatalf("failed to store request: %v", err)
return req, nil
},
StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
return nil
},
} }
svc := sender.NewService(sender.Config{
Repository: repoMock,
})
exp := reqlog.ResponseLog{ svc := sender.NewService(sender.Config{
ReqLogService: reqlog.NewService(reqlog.Config{
Repository: db,
}),
Repository: db,
})
svc.SetActiveProjectID(projectID)
exp := &reqlog.ResponseLog{
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Status: "200 OK", Status: "200 OK",
@ -263,27 +299,8 @@ func TestSendRequest(t *testing.T) {
t.Fatalf("unexpected error sending request: %v", err) t.Fatalf("unexpected error sending request: %v", err)
} }
if len(repoMock.FindSenderRequestByIDCalls()) != 1 { diff := cmp.Diff(exp, got.Response, cmpopts.IgnoreFields(sender.Request{}, "ID"))
t.Fatal("expected `svc.repo.FindSenderRequestByID()` to have been called 1 time") if diff != "" {
} t.Fatalf("request not equal (-exp, +got):\n%v", diff)
if diff := cmp.Diff(reqID, repoMock.FindSenderRequestByIDCalls()[0].ID); diff != "" {
t.Fatalf("call arg `id` for `svc.repo.FindSenderRequestByID()` not equal (-exp, +got):\n%v", diff)
}
if len(repoMock.StoreResponseLogCalls()) != 1 {
t.Fatal("expected `svc.repo.StoreResponseLog()` to have been called 1 time")
}
if diff := cmp.Diff(reqID, repoMock.StoreResponseLogCalls()[0].ReqLogID); diff != "" {
t.Fatalf("call arg `reqLogID` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
}
if diff := cmp.Diff(exp, repoMock.StoreResponseLogCalls()[0].ResLog); diff != "" {
t.Fatalf("call arg `resLog` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
}
if diff := cmp.Diff(repoMock.StoreResponseLogCalls()[0].ResLog, *got.Response); diff != "" {
t.Fatalf("returned response log value and persisted value not equal (-exp, +got):\n%v", diff)
} }
} }

View File

@ -5,5 +5,4 @@ package tools
import ( import (
_ "github.com/99designs/gqlgen" _ "github.com/99designs/gqlgen"
_ "github.com/matryer/moq"
) )