diff --git a/.dockerignore b/.dockerignore index 65959251..3eb84618 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,24 @@ .editorconfig .git +.gitignore .github sonar-project.properties -AUTHORS.md -CONTRIBUTING.md +*.md LICENSE Makefile NOTICE -README.md arm/ powerpc/ mips/ .golangci.yml -_temp .vscode -node1 -node2 -node3 -.gitignore -changelog.md +go.work +go.work.sum +tools/ +test_e2e/ +!test_e2e/test_api.go +!test_e2e/go.mod +!test_e2e/go.sum +mocks/ +docker/ +**/*_test.go \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 31914924..9cc652d2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,30 +5,33 @@ title: '' labels: Bug --- -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. -**To Reproduce** +## To Reproduce Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Screenshots** +## Screenshots If applicable, add screenshots to help explain your problem. -**Application (please complete the following information):** +## Application + +Please complete the following information: - badaas version [X.X.X] or commit hash -**Additional context** +## Additional context Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/user_story.md b/.github/ISSUE_TEMPLATE/user_story.md index ec61755b..1112fab2 100644 --- a/.github/ISSUE_TEMPLATE/user_story.md +++ b/.github/ISSUE_TEMPLATE/user_story.md @@ -26,7 +26,7 @@ labels: User Story, To be verify `[Put all others constraints here, like list of acceptances values or other]` -## Resources: +## Resources `[Put all your resources here, like mockups, diagrams or other here]` @@ -37,4 +37,3 @@ labels: User Story, To be verify ## Links `[Only use by the team, to link this feature with epic, technical tasks or bugs]` - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c2d7558b..210601a1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ :information_source: Don't forget to modify the changelog.md before merging this branch. :information_source: Don't forget to modify config files: -- `badaas.example.yml`: the example file. -- `/scripts/e2e/api/ci-conf.yml`: otherwise you will probably break the CI. (*For local testing please use [act](https://github.com/nektos/act)*). \ No newline at end of file + +- `badaas.example.yml`: the example file. diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e382df70..5d8a6213 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,6 +5,7 @@ on: - main pull_request: types: [opened, synchronize, reopened] + jobs: branch-naming-rules: name: Check branch name @@ -18,6 +19,26 @@ jobs: min_length: 5 max_length: 50 + check-style: + name: Code style + needs: [branch-naming-rules] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-go@v3 + with: + go-version: '^1.18' + cache: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.52.2 + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true + unit-tests: name: Unit tests needs: [branch-naming-rules] @@ -30,16 +51,25 @@ jobs: with: go-version: '^1.18' cache: true - - name: Run test - run: go test $(go list ./... | sed 1d) -coverprofile=coverage.out -v + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + - name: Run unit tests + run: gotestsum --junitfile unit-tests.xml $(go list ./... | grep -v testintegration) -coverpkg=./... -coverprofile=coverage_unit.out - uses: actions/upload-artifact@v3 with: name: coverage - path: coverage.out + path: coverage_unit.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed + with: + name: Unit Tests Report # Name of the check run which will be created + path: unit-tests.xml # Path to test results + reporter: java-junit # Format of test results - check-style: - name: Code style - needs: [branch-naming-rules] + integration-tests: + name: Integration tests + needs: [unit-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -49,14 +79,25 @@ jobs: with: go-version: '^1.18' cache: true - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + - name: Start containers + run: docker compose -f "docker/test_db/docker-compose.yml" up -d + - name: Run test + run: gotestsum --junitfile integration-tests.xml ./testintegration -coverpkg=./... -coverprofile=coverage_int.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed with: - version: latest - skip-cache: true - skip-pkg-cache: true - skip-build-cache: true + name: Integration Tests Report # Name of the check run which will be created + path: integration-tests.xml # Path to test results + reporter: java-junit # Format of test results + - uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage_int.out + - name: Stop containers + run: docker stop badaas-test-db e2e-tests: name: E2E Tests @@ -71,41 +112,39 @@ jobs: go-version: '^1.18' cache: true - name: Start containers - run: docker compose -f "scripts/e2e/docker-compose.yml" up -d --build + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d --build - name: Wait for API server to be up uses: mydea/action-wait-for-api@v1 with: url: "http://localhost:8000/info" - timeout: 20 + timeout: 60 - name: Run test - run: go test -v + run: go test ./test_e2e -v - name: Get logs if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" logs --no-color 2>&1 | tee app.log & + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" logs --no-color 2>&1 | tee app.log & - name: Stop containers if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" down + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" down - uses: actions/upload-artifact@v3 with: name: docker-compose-e2e-logs path: app.log - + sonarcloud: name: SonarCloud - needs: [unit-tests, check-style] + needs: [check-style, integration-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Download line coverage report + - name: Download line coverage reports uses: actions/download-artifact@v3 with: name: coverage - path: coverage.out - name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d477eb47..4210c0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -13,21 +13,19 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +*-tests*.xml # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ # Go workspace file -go.work +# go.work # cockroach files node* -#Vscode conf +# vscode conf .vscode # binary output -badaas - -# temporary directories -_temp \ No newline at end of file +badaas \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d252da0..00a4d76a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,10 @@ # Contribute to the development of badaas - [Tests](#tests) + - [Dependencies](#dependencies) - [Unit tests](#unit-tests) - - [Feature tests (of end to end tests)](#feature-tests-of-end-to-end-tests) + - [Integration tests](#integration-tests) + - [Feature tests (of end to end tests)](#feature-tests-or-end-to-end-tests) - [Logger](#logger) - [Directory structure](#directory-structure) - [Git](#git) @@ -12,74 +14,78 @@ ## Tests +### Dependencies + +Running tests have some dependencies as: `mockery`, `gotestsum`, etc.. Install them with `make install dependencies`. + ### Unit tests -We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `mockery --all --keeptree`. +We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `make test_generate_mocks`. To run them, please run: ```sh -go test $(go list ./... | sed 1d) -v +make test_unit ``` -### Feature tests (of end to end tests) +### Integration tests -We use docker to run a Badaas instance in combination with one node of CockroachDB. - -Run: +Integration tests have a database and the dependency injection system. ```sh -docker compose -f "scripts/e2e/docker-compose.yml" up -d --build +make test_integration ``` -Then in an another shell: +### Feature tests (or end to end tests) + +We use docker to run a Badaas instance in combination with one node of CockroachDB. + +Run: ```sh -go test -v +make test_e2e ``` -The feature files can be found in the `feature` folder. +The feature files can be found in the `test_e2e/features` folder. ## Logger -We use ubber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. +We use uber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. ## Directory structure This is the directory structure we use for the project: -- `commands/` *(Go code)*: Contains all the CLI commands. This package relies heavily on github.com/ditrit/verdeter. -- `configuration/` *(Go code)*: Contains all the configuration holders. Please only use the interfaces, they are all mocked for easy testing +- `configuration/` *(Go code)*: Contains all the configuration keys and holders. Please only use the interfaces, they are all mocked for easy testing. - `controllers/` *(Go code)*: Contains all the http controllers, they handle http requests and consume services. -- `docs/`: Contains the documentation. -- `features/`: Contains all the feature tests (or end to end tests). +- `docker/` : Contains the docker, docker-compose file and configuration files for different environments. + - `test_db/` : Contains the Dockerfile to build a development/test version of CockroachDB. + - `test_api/` : Contains files to build a development/test version of the api. +- `test_e2e/`: Contains all the feature and steps for e2e tests. +- `testintegration/`: Contains all the integration tests. - `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. -- `persistance/` *(Go code)*: - - `/gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - - `/models/` *(Go code)*: Contains the models. (For a structure to me considered a valid model, it has to embed `models.BaseModel` and satisfy the `models.Tabler` interface. This interface returns the name of the sql table.) - - `/dto/` *(Go code)*: Contains the Data Transfert Objects. They are used mainly to decode json payloads. - - `/pagination/` *(Go code)*: Contains the pagination logic. - - `/repository/` *(Go code)*: Contains the repository interface and implementation. Use uint as ID when using gorm models. -- `resources/` *(Go code)*: Contains the resources shared with the rest of the codebase (ex: API version). +- `orm/` *(Go code)*: Contains the code of the orm used by badaas. +- `persistance/` *(Go code)*: + - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. + - `models/` *(Go code)*: Contains the models (for a structure to me considered a valid model, it has to embed `badaas/orm.UUIDModel` or `badaas/orm.UIntModel`). + - `dto/` *(Go code)*: Contains the Data Transfer Objects. They are used mainly to decode json payloads. + - `pagination/` *(Go code)*: Contains the pagination logic. + - `repository/` *(Go code)*: Contains the repository interfaces and implementations to manage queries to the database. - `router/` *(Go code)*: Contains http router of badaas. - - `/middlewares/` *(Go code)*: Contains the various http middlewares that we use. -- `scripts/e2e/` : Contains the docker-compose file for end-to-end test. - - `/api/` : Contains the Dockerfile to build badaas with a dedicated config file. - - `/db/` : Contains the Dockerfile to build a developpement version of CockroachDB. -- `services/` *(Go code)*: Contains the Dockerfile to build a developpement version of CockroachDB. - - `/auth/protocols/`: Contains the implementations of authentication clients for differents protocols. - - `/basicauth/` *(Go code)*: Handle the authentification using email/password. - - `/oidc/` *(Go code)*: Handle the authentication via Open-ID Connect. - - `/sessionservice/` *(Go code)*: Handle sessions and their lifecycle. - - `/userservice/` *(Go code)*: Handle users. - - `validators/` *(Go code)*: Contains validators such as an email validator. + - `middlewares/` *(Go code)*: Contains the various http middlewares that we use. +- `services/` *(Go code)*: Contains services. + - `auth/protocols/`: Contains the implementations of authentication clients for different protocols. + - `basicauth/` *(Go code)*: Handle the authentication using email/password. + - `sessionservice/` *(Go code)*: Handle sessions and their lifecycle. + - `userservice/` *(Go code)*: Handle users. +- `utils/` *(Go code)*: Contains utility functions that can be used all around the project. +- `validators/` *(Go code)*: Contains validators such as an email validator. At the root of the project, you will find: - The README. - The changelog. -- The files for the E2E test http support. -- The LICENCE file. +- The LICENSE file. ## Git @@ -87,17 +93,16 @@ At the root of the project, you will find: `[BRANCH_TYPE]/[BRANCH_NAME]` -- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. - +- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. Accepted prefixes are: - `feature`, used for feature development - `bugfix`, used for bug fix - - `improvement`, used for refacto + - `improvement`, used for refactor - `library`, used for updating library - `prerelease`, used for preparing the branch for the release - `release`, used for releasing project - `hotfix`, used for applying a hotfix on main - - `poc`, used for proof of concept + - `poc`, used for proof of concept - `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). ### Default branch @@ -113,9 +118,9 @@ We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline f Steps to release: - Create a new branch labeled `release/vX.Y.Z` from the latest `main`. -- Improve the version number in `changelog.md` and `resources/api.go`. +- Improve the version number in `changelog.md`. - Verify the content of the `changelog.md`. - Commit the modifications with the label `Release version X.Y.Z`. - Create a pull request on github for this branch into `main`. -- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z` -- After the tag is pushed, make the release on the tag in GitHub +- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z`. +- After the tag is pushed, make the release on the tag in GitHub. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 96dc2afe..00000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -WORKDIR /app -COPY . . -RUN apk add build-base -RUN CGO_ENABLED=1 go build -a -o badaas . - - -# final image for end users -FROM alpine:3.16.2 -COPY --from=builder /app/badaas . -EXPOSE 8000 -ENTRYPOINT [ "./badaas" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2d29efc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +PATHS = $(shell go list ./... | grep -v testintegration) + +install_dependencies: + go install gotest.tools/gotestsum@latest + go install github.com/vektra/mockery/v2@v2.20.0 + go install github.com/ditrit/badaas-cli@latest + +lint: + golangci-lint run + +test_unit: + gotestsum --format pkgname $(PATHS) + +test_integration: + docker compose -f "docker/test_db/docker-compose.yml" up -d + ./docker/wait_for_db.sh + gotestsum --format testname ./testintegration + +test_e2e: + docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d + ./docker/wait_for_api.sh 8000/info + go test ./test_e2e -v + +test_generate_mocks: + mockery --all --keeptree + +.PHONY: test_unit test_integration test_e2e + diff --git a/README.md b/README.md index 15ade58d..2bdcffe4 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,162 @@ # BADAAS: Backend And Distribution As A Service -Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). +Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). -Badaas provides several key features: - -- **Authentification**: Badaas can authentify users using its internal authentification scheme or externally by using protocols such as OIDC, SAML, Oauth2... -- **Habilitation**: On a resource access, Badaas will check if the user is authorized using a RBAC model. -- **Distribution**: Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). -- **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared accross the clusters to increase resiliency. -- **Querying Resources**: Resources are accessible via a REST API. -- **Posix complient**: Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) -- **Advanced logs management**: Badaas provides an interface to interact with the logs produced by the clusters. Logs are formated in json by default. - -To quickly get badaas up and running, please head to the [miniblog tutorial]() +> **Warning** +> BaDaaS is still under development. Each of its components can have a different state of evolution that you can consult in [Features and components](#features-and-components) +- [Features and components](#features-and-components) - [Quickstart](#quickstart) -- [Docker install](#docker-install) -- [Install from sources](#install-from-sources) - - [Prerequisites](#prerequisites) - - [Configuration](#configuration) + - [Example](#example) + - [Step-by-step instructions](#step-by-step-instructions) +- [Configuration](#configuration) - [Contributing](#contributing) -- [Licence](#licence) +- [License](#license) + +## Features and components + +Badaas provides several key features, each provided by a component that can be used independently and has a different state of evolution: + +- **Authentication**(unstable): Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authorization**(wip_unstable): On resource access, Badaas will check if the user is authorized using a RBAC model. +- **Distribution**(todo): Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). +- **Persistence**(wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. To achieve this, BaDaaS uses the [BaDaaS ORM](https://github.com/ditrit/badaas/orm) component. +- **Querying Resources**(unstable): Resources are accessible via a REST API. +- **Posix compliant**(stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) +- **Advanced logs management**(todo): Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. ## Quickstart -You can either use the [Docker Install](#docker-install) or build it from source . +### Example -## Docker install +To quickly get badaas up and running, you can head to the [example](https://github.com/ditrit/badaas-example). This example will help you to see how to use badaas and as a template to start your own project -You can build the image using `docker build -t badaas .` since we don't have an official docker image yet. +### Step-by-step instructions -## Install from sources +Once you have started your project with `go init`, you must add the dependency to badaas: -### Prerequisites +```bash +go get -u github.com/ditrit/badaas +``` -Get the sources of the project, either by visiting the [releases](https://github.com/ditrit/badaas/releases) page and downloading an archive or clone the main branch (please be aware that is it not a stable version). +Then, you can use the following structure to configure and start your application + +```go +func main() { + badaas.BaDaaS.AddModules( + // add badaas modules + ).Provide( + // provide constructors + ).Invoke( + // invoke functions + ).Start() +} +``` -To build the project: +#### Config badaas functionalities -- [Install go](https://go.dev/dl/#go1.18.4) v1.18 -- Install project dependencies +You are free to choose which badaas functionalities you wish to use. To add them, you must add the corresponding module, for example: -```bash -go get +```go +func main() { + badaas.BaDaaS.AddModules( + badaas.InfoModule, + badaas.AuthModule, + ).Provide( + NewAPIVersion, + ).Start() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} ``` -- Run build command +#### Add your own functionalities + +With the "Provide" and "Invoke" functions you will be able to add your own functionalities to the application. For example, to add a route you must first provide the constructor of the controller and then invoke the function that adds this route: + +```go +func main() { + badaas.BaDaaS.Provide( + NewHelloController, + ).Invoke( + AddExampleRoutes, + ).Start() +} + +type HelloController interface { + SayHello(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) +} + +type helloControllerImpl struct{} + +func NewHelloController() HelloController { + return &helloControllerImpl{} +} + +func (*helloControllerImpl) SayHello(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return "hello world", nil +} + +func AddExampleRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + helloController HelloController, +) { + router.HandleFunc( + "/hello", + jsonController.Wrap(helloController.SayHello), + ).Methods("GET") +} +``` -```bash -go build . +#### Run it + +Once you have defined the functionalities of your project (the `/hello` route in this case), you can run the application using the steps described in the example README.md + +### Provided functionalities + +#### InfoModule + +`InfoModule` adds the path `/info`, where the api version will be answered. To set the version you want to be responded you must provide a function that returns it: + +```go +func main() { + badaas.BaDaaS.AddModules( + badaas.InfoModule, + ).Provide( + NewAPIVersion, + ).Start() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} ``` -Well done, you have a binary `badaas` at the root of the project. +#### AuthModule -Then you can launch Badaas directly with: +`AuthModule` adds `/login` and `/logout`, which allow us to add authentication to our application in a simple way: -```bash -export BADAAS_DATABASE_PORT= -export BADAAS_DATABASE_HOST= -export BADAAS_DATABASE_DBNAME= -export BADAAS_DATABASE_SSLMODE= -export BADAAS_DATABASE_USERNAME= -export BADAAS_DATABASE_PASSWORD= -./badaas +```go +func main() { + badaas.BaDaaS.AddModules( + badaas.AuthModule, + ).Start() +} ``` ### Configuration -Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration. So Badaas is POSIX complient by default. +Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration, so Badaas is POSIX compliant by default. + +Badgen automatically generates a default configuration in `badaas/config/badaas.yml`, but you are free to modify it if you need to. -Badaas can be configured using environment variables, configuration files or CLI flags. +This can be done using environment variables, configuration files or CLI flags. CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. -As an exemple we will define the `database.port` configuration key using the 3 methods: +As an example we will define the `database.port` configuration key using the 3 methods: - Using a CLI flag: `--database.port=1222` - Using an environment variable: `export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) @@ -81,7 +165,7 @@ As an exemple we will define the `database.port` configuration key using the 3 m ```yml # /etc/badaas/badaas.yml database: - port: 1222 + port: 1222 ``` The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` or in the same folder as the badaas binary `./badaas.yml`. @@ -94,6 +178,6 @@ If needed, the location can be overridden using the config key `config_path`. See [this section](./CONTRIBUTING.md). -## Licence +## License -Badaas is Licenced under the [Mozilla Public License Version 2.0](./LICENSE). +Badaas is Licensed under the [Mozilla Public License Version 2.0](./LICENSE). diff --git a/badaas.example.yml b/badaas.example.yml index 44313081..1c80e94a 100644 --- a/badaas.example.yml +++ b/badaas.example.yml @@ -8,6 +8,9 @@ database: # (mandatory) port: 26257 + # The name of the database to use. + name: badaas_db + # The sslmode of the connection to the database server. # (mandatory) sslmode: disable @@ -34,7 +37,7 @@ database: server: # The address to bind badaas to. # default ("0.0.0.0") - host: "" + host: "" # The port badaas should use. # default (8000) @@ -42,7 +45,7 @@ server: # The maximum timeout for the http server in seconds. # default (15) - timeout: 15 + timeout: 15 # The settings for the pagination. pagination: @@ -61,7 +64,7 @@ logger: template: "Receive {{method}} request on {{url}}" # The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. +# This section contains some good defaults, don't change those value unless you need to. session: # The duration of a user session, in seconds # Default (14400) equal to 4 hours @@ -78,5 +81,4 @@ default: # The admin settings for the first run admin: # The admin password for the first run. Won't change is the admin user already exists. - password: admin - \ No newline at end of file + password: admin \ No newline at end of file diff --git a/badaas.go b/badaas.go index 05513376..af29a837 100644 --- a/badaas.go +++ b/badaas.go @@ -1,11 +1,87 @@ -// Package main : -package main +package badaas import ( - "github.com/ditrit/badaas/commands" + "net/http" + + "github.com/spf13/cobra" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/persistence" + "github.com/ditrit/badaas/router" + "github.com/ditrit/verdeter" ) -// Badaas application, run a http-server on 8000. -func main() { - commands.Execute() +var BaDaaS = BaDaaSInitializer{} + +type BaDaaSInitializer struct { + modules []fx.Option +} + +// Allows to select which modules provided by badaas must be added to the application +func (badaas *BaDaaSInitializer) AddModules(modules ...fx.Option) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, modules...) + + return badaas +} + +// Allows to provide constructors to the application +// so that the constructed objects will be available via dependency injection +func (badaas *BaDaaSInitializer) Provide(constructors ...any) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, fx.Provide(constructors...)) + + return badaas +} + +// Allows to invoke functions when the application starts. +// They can take advantage of dependency injection +func (badaas *BaDaaSInitializer) Invoke(funcs ...any) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, fx.Invoke(funcs...)) + + return badaas +} + +// Start the application +func (badaas BaDaaSInitializer) Start() { + rootCommand := verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "BaDaaS", + Run: badaas.runHTTPServer, + }) + + err := configuration.NewCommandInitializer(configuration.NewKeySetter()).Init(rootCommand) + if err != nil { + panic(err) + } + + rootCommand.Execute() +} + +// Run the http server for badaas +func (badaas BaDaaSInitializer) runHTTPServer(cmd *cobra.Command, args []string) { + modules := []fx.Option{ + // internal modules + configuration.ConfigurationModule, + router.RouterModule, + logger.LoggerModule, + persistence.PersistanceModule, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + // create httpServer + fx.Provide(newHTTPServer), + // Finally: we invoke the newly created server + fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), + } + + fx.New( + // add modules selected by user + append(modules, badaas.modules...)..., + ).Run() } diff --git a/badaas_e2e_test.go b/badaas_e2e_test.go deleted file mode 100644 index 8b5e2072..00000000 --- a/badaas_e2e_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "net/http" - "net/http/cookiejar" - "os" - "testing" - "time" - - "github.com/cucumber/godog" - "github.com/cucumber/godog/colors" - "github.com/spf13/pflag" -) - -type TestContext struct { - statusCode int - json map[string]interface{} - httpClient *http.Client -} - -var opts = godog.Options{Output: colors.Colored(os.Stdout)} - -func init() { - godog.BindCommandLineFlags("godog.", &opts) -} - -func TestMain(m *testing.M) { - pflag.Parse() - opts.Paths = pflag.Args() - - status := godog.TestSuite{ - Name: "godogs", - ScenarioInitializer: InitializeScenario, - Options: &opts, - }.Run() - - os.Exit(status) -} - -func InitializeScenario(ctx *godog.ScenarioContext) { - t := &TestContext{} - jar, err := cookiejar.New(nil) - if err != nil { - panic(err) - } - t.httpClient = &http.Client{ - Transport: http.DefaultTransport, - Timeout: time.Duration(5 * time.Second), - Jar: jar, - } - - ctx.Step(`^I request "(.+)"$`, t.requestGET) - ctx.Step(`^I expect status code is "(\d+)"$`, t.assertStatusCode) - ctx.Step(`^I expect response field "(.+)" is "(.+)"$`, t.assertResponseFieldIsEquals) - ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJson) -} diff --git a/badaas_test.go b/badaas_test.go new file mode 100644 index 00000000..cae48d52 --- /dev/null +++ b/badaas_test.go @@ -0,0 +1,68 @@ +package badaas + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/mock" + "go.uber.org/fx" + + "github.com/ditrit/badaas/configuration" +) + +func TestInvokeFunctionsWithProvidedValues(t *testing.T) { + mockObject := mockObject{} + + mockObject.On("Function", 1).Return(1) + + viper.Set(configuration.DatabasePortKey, 5000) + viper.Set(configuration.DatabaseHostKey, "localhost") + viper.Set(configuration.DatabaseUsernameKey, "badaas") + viper.Set(configuration.DatabasePasswordKey, "badaas") + viper.Set(configuration.DatabaseSslmodeKey, "disable") + viper.Set(configuration.DatabaseRetryKey, 0) + + badaas := BaDaaSInitializer{} + badaas.Provide( + newIntValue, + ).Invoke( + mockObject.Function, + shutdown, + ).Start() +} + +func TestAddModulesAreExecuted(t *testing.T) { + mockObject := mockObject{} + + mockObject.On("Function", 1).Return(1) + + badaas := BaDaaSInitializer{} + badaas.AddModules( + fx.Module( + "test module", + fx.Provide(newIntValue), + fx.Invoke(mockObject.Function), + ), + ).Invoke( + shutdown, + ).Start() +} + +func newIntValue() int { + return 1 +} + +type mockObject struct { + mock.Mock +} + +func (o *mockObject) Function(intValue int) int { + args := o.Called(intValue) + return args.Int(0) +} + +func shutdown( + shutdowner fx.Shutdowner, +) { + shutdowner.Shutdown() +} diff --git a/changelog.md b/changelog.md index 866b2573..24def010 100644 --- a/changelog.md +++ b/changelog.md @@ -9,27 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Setup project (ci and sonar) +- Setup project (ci and sonar). - Setup e2e test solution (cucumber + docker). - Setup Docker based build system. -- Add default api endpoint `info` -- Setup command based pattern using verdeter -- Add an http error handling mecanism -- Add a json controller -- Add a dto package +- Add default api endpoint `info`. +- Setup command based pattern using verdeter. +- Add an http error handling mechanism. +- Add a json controller. +- Add a dto package. - The tasks in the CI are ran in parallel. -- Add a Generic CRUD Repository +- Add a Generic CRUD Repository. - Add a configuration structure containing all the configuration holder. - Refactor codebase to use the DI framework uber-go/fx. Now all services and controllers relies on interfaces. -- Add an generic ID to the repository interface -- Add a retry mecanism for the database connection +- Add an generic ID to the repository interface. +- Add a retry mechanism for the database connection. - Add `init` flag to migrate database and create admin user. - Add a CONTRIBUTING.md and a documentation file for configuration (configuration.md) - Add a session services. -- Add a basic authentification controller. +- Add a basic authentication controller. - Now config keys are only declared once with constants in the `configuration/` package. - Add a dto that is returned on a successful login. -- Update verdeter to version v0.4.0 +- Update verdeter to version v0.4.0. +- Transform BadAas into a library. +- Add badaas-orm with the compilable query system. +- Add operators support - -[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased \ No newline at end of file +[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased diff --git a/commands/initDatabaseCommands.go b/commands/initDatabaseCommands.go deleted file mode 100644 index 022cfb98..00000000 --- a/commands/initDatabaseCommands.go +++ /dev/null @@ -1,32 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initDatabaseCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.DatabasePortKey, verdeter.IsInt, "", "The port of the database server") - cfg.SetRequired(configuration.DatabasePortKey) - - cfg.GKey(configuration.DatabaseHostKey, verdeter.IsStr, "", "The host of the database server") - cfg.SetRequired(configuration.DatabaseHostKey) - - cfg.GKey(configuration.DatabaseNameKey, verdeter.IsStr, "", "The name of the database to use") - cfg.SetRequired(configuration.DatabaseNameKey) - - cfg.GKey(configuration.DatabaseUsernameKey, verdeter.IsStr, "", "The username of the account on the database server") - cfg.SetRequired(configuration.DatabaseUsernameKey) - - cfg.GKey(configuration.DatabasePasswordKey, verdeter.IsStr, "", "The password of the account one the database server") - cfg.SetRequired(configuration.DatabasePasswordKey) - - cfg.GKey(configuration.DatabaseSslmodeKey, verdeter.IsStr, "", "The sslmode to use when connecting to the database server") - cfg.SetRequired(configuration.DatabaseSslmodeKey) - - cfg.GKey(configuration.DatabaseRetryKey, verdeter.IsUint, "", "The number of times badaas tries to establish a connection with the database") - cfg.SetDefault(configuration.DatabaseRetryKey, uint(10)) - - cfg.GKey(configuration.DatabaseRetryDurationKey, verdeter.IsUint, "", "The duration in seconds badaas wait between connection attempts") - cfg.SetDefault(configuration.DatabaseRetryDurationKey, uint(5)) -} diff --git a/commands/initInitialisationCommands.go b/commands/initInitialisationCommands.go deleted file mode 100644 index 130e9778..00000000 --- a/commands/initInitialisationCommands.go +++ /dev/null @@ -1,13 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initInitialisationCommands(cfg *verdeter.VerdeterCommand) { - - cfg.GKey(configuration.InitializationDefaultAdminPasswordKey, verdeter.IsStr, "", - "Set the default admin password is the admin user is not created yet.") - cfg.SetDefault(configuration.InitializationDefaultAdminPasswordKey, "admin") -} diff --git a/commands/initLoggerCommands.go b/commands/initLoggerCommands.go deleted file mode 100644 index 097ac1d3..00000000 --- a/commands/initLoggerCommands.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initLoggerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.LoggerModeKey, verdeter.IsStr, "", "The logger mode (default to \"prod\")") - cfg.SetDefault(configuration.LoggerModeKey, "prod") - cfg.AddValidator(configuration.LoggerModeKey, validators.AuthorizedValues("prod", "dev")) - - cfg.GKey(configuration.LoggerRequestTemplateKey, verdeter.IsStr, "", "Template message for all request logs") - cfg.SetDefault(configuration.LoggerRequestTemplateKey, "Receive {{method}} request on {{url}}") -} diff --git a/commands/initServerCommands.go b/commands/initServerCommands.go deleted file mode 100644 index 7323524b..00000000 --- a/commands/initServerCommands.go +++ /dev/null @@ -1,23 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initServerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.ServerTimeoutKey, verdeter.IsInt, "", "Maximum timeout of the http server in second (default is 15s)") - cfg.SetDefault(configuration.ServerTimeoutKey, 15) - - cfg.GKey(configuration.ServerHostKey, verdeter.IsStr, "", "Address to bind (default is 0.0.0.0)") - cfg.SetDefault(configuration.ServerHostKey, "0.0.0.0") - - cfg.GKey(configuration.ServerPortKey, verdeter.IsInt, "p", "Port to bind (default is 8000)") - cfg.AddValidator(configuration.ServerPortKey, validators.CheckTCPHighPort) - cfg.SetDefault(configuration.ServerPortKey, 8000) - - cfg.GKey(configuration.ServerPaginationMaxElemPerPage, verdeter.IsUint, "", "The max number of records returned per page") - cfg.SetDefault(configuration.ServerPaginationMaxElemPerPage, 100) - -} diff --git a/commands/initSessionCommands.go b/commands/initSessionCommands.go deleted file mode 100644 index 13f4ad74..00000000 --- a/commands/initSessionCommands.go +++ /dev/null @@ -1,19 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -// initialize session related config keys -func initSessionCommands(cfg *verdeter.VerdeterCommand) { - cfg.LKey(configuration.SessionDurationKey, verdeter.IsUint, "", "The duration of a user session in seconds.") - cfg.SetDefault(configuration.SessionDurationKey, uint(3600*4)) // 4 hours by default - - cfg.LKey(configuration.SessionPullIntervalKey, - verdeter.IsUint, "", "The refresh interval in seconds. Badaas refresh it's internal session cache periodically.") - cfg.SetDefault(configuration.SessionPullIntervalKey, uint(30)) // 30 seconds by default - - cfg.LKey(configuration.SessionRollIntervalKey, verdeter.IsUint, "", "The interval in which the user can renew it's session by making a request.") - cfg.SetDefault(configuration.SessionRollIntervalKey, uint(3600)) // 1 hour by default -} diff --git a/commands/r.md b/commands/r.md deleted file mode 100644 index c32746b4..00000000 --- a/commands/r.md +++ /dev/null @@ -1,86 +0,0 @@ -package commands - -import ( - "log" - - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence/registry" - "github.com/ditrit/badaas/persistence/repository" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/session" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "go.uber.org/zap" -) - -// Create a super admin user and exit with code 1 on error -func createSuperAdminUser() { - logg := zap.L().Sugar() - _, err := userservice.NewUser("superadmin", "superadmin@badaas.test", "1234") - if err != nil { - if repository.ErrAlreadyExists == err { - logg.Debugf("The superadmin user already exists in database") - } else { - logg.Fatalf("failed to save the super admin %w", err) - } - } - -} - -// Run the http server for badaas -func runHTTPServer(cfg *verdeter.VerdeterCommand, args []string) error { - err := logger.InitLoggerFromConf() - if err != nil { - log.Fatalf("An error happened while initializing logger (ERROR=%s)", err.Error()) - } - - zap.L().Info("The logger is initialiazed") - - // create router - router := router.SetupRouter() - - registryInstance, err := registry.FactoryRegistry(registry.GormDataStore) - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing datastorage layer (ERROR=%s)", err.Error()) - } - registry.ReplaceGlobals(registryInstance) - zap.L().Info("The datastorage layer is initialized") - - createSuperAdminUser() - - err = session.Init() - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing the session service (ERROR=%s)", err.Error()) - } - zap.L().Info("The session service is initialized") - - // create server - srv := createServerFromConfiguration(router) - - zap.L().Sugar().Infof("Ready to serve at %s\n", srv.Addr) - return srv.ListenAndServe() -} - -var rootCfg = verdeter.NewVerdeterCommand( - "badaas", - "Backend and Distribution as a Service", - `Badaas stands for Backend and Distribution as a Service.`, - runHTTPServer, -) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.Initialize() - - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) -} diff --git a/commands/rootCmd.go b/commands/rootCmd.go deleted file mode 100644 index 605b3bec..00000000 --- a/commands/rootCmd.go +++ /dev/null @@ -1,70 +0,0 @@ -package commands - -import ( - "net/http" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence" - "github.com/ditrit/badaas/resources" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/sessionservice" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "github.com/spf13/cobra" - "go.uber.org/fx" - "go.uber.org/fx/fxevent" - "go.uber.org/zap" -) - -// Run the http server for badaas -func runHTTPServer(cmd *cobra.Command, args []string) { - fx.New( - // Modules - configuration.ConfigurationModule, - router.RouterModule, - controllers.ControllerModule, - logger.LoggerModule, - persistence.PersistanceModule, - - fx.Provide(userservice.NewUserService), - fx.Provide(sessionservice.NewSessionService), - // logger for fx - fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { - return &fxevent.ZapLogger{Logger: logger} - }), - - fx.Provide(NewHTTPServer), - - // Finally: we invoke the newly created server - fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), - fx.Invoke(createSuperUser), - ).Run() -} - -// The command badaas -var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ - Use: "badaas", - Short: "Backend and Distribution as a Service", - Long: "Badaas stands for Backend and Distribution as a Service.", - Version: resources.Version, - Run: runHTTPServer, -}) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) - initInitialisationCommands(rootCfg) - initSessionCommands(rootCfg) -} diff --git a/commands/server.go b/commands/server.go deleted file mode 100644 index 1d023b42..00000000 --- a/commands/server.go +++ /dev/null @@ -1,71 +0,0 @@ -package commands - -// This file holds functions needed by the badaas rootCommand, thoses functions help in creating the http.Server. - -import ( - "context" - "fmt" - "net" - "net/http" - "time" - - "go.uber.org/fx" - "go.uber.org/zap" - - "github.com/ditrit/badaas/configuration" -) - -// Create the server from the configuration holder and the http handler -func createServerFromConfigurationHolder(router http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { - address := addrFromConf(httpServerConfig.GetHost(), httpServerConfig.GetPort()) - timeout := httpServerConfig.GetMaxTimeout() - return createServer(router, address, timeout, timeout) -} - -// Create an http server -func createServer(router http.Handler, address string, writeTimeout, readTimeout time.Duration) *http.Server { - srv := &http.Server{ - Handler: router, - Addr: address, - - WriteTimeout: writeTimeout, - ReadTimeout: readTimeout, - } - return srv -} - -// Create the addr string for the http.Server -// returns ":" -func addrFromConf(host string, port int) string { - address := fmt.Sprintf("%s:%d", - host, - port, - ) - return address -} - -func NewHTTPServer( - lc fx.Lifecycle, - logger *zap.Logger, - router http.Handler, - httpServerConfig configuration.HTTPServerConfiguration, -) *http.Server { - srv := createServerFromConfigurationHolder(router, httpServerConfig) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - ln, err := net.Listen("tcp", srv.Addr) - if err != nil { - return err - } - logger.Sugar().Infof("Ready to serve at %s", srv.Addr) - go srv.Serve(ln) - return nil - }, - OnStop: func(ctx context.Context) error { - // Flush the logger - logger.Sync() - return srv.Shutdown(ctx) - }, - }) - return srv -} diff --git a/commands/server_test.go b/commands/server_test.go deleted file mode 100644 index aa949b71..00000000 --- a/commands/server_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package commands - -// This files holds the tests for the commands/server.go file. - -import ( - "net/http" - "testing" - "time" - - "github.com/ditrit/badaas/configuration" - "github.com/stretchr/testify/assert" -) - -func Test_addrFromConf(t *testing.T) { - expected := "192.168.236.222:25100" - addr := addrFromConf("192.168.236.222", 25100) - assert.Equal(t, expected, addr) -} -func Test_createServer(t *testing.T) { - handl := http.NewServeMux() - timeout := time.Duration(time.Second) - srv := createServer( - handl, - "localhost:8000", - timeout, timeout, - ) - assert.NotNil(t, srv) -} - -func TestCreateServerFromConfigurationHolder(t *testing.T) { - handl := http.NewServeMux() - - srv := createServerFromConfigurationHolder(handl, configuration.NewHTTPServerConfiguration()) - assert.NotNil(t, srv) - -} diff --git a/configuration.md b/configuration.md index 06383628..20b7652a 100644 --- a/configuration.md +++ b/configuration.md @@ -1,15 +1,15 @@ # Configuration -To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml) +To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml). As said in the README: > Badaas can be configured using environment variables, CLI flags or a configuration file. > CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. -In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environement variables and CLI flags to change Badaas' config. +In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environment variables and CLI flags to change Badaas' config. -The config file can be formated in any syntax that [github.com/spf13/viper](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. +The config file can be formatted in any syntax that [`viper`](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. - [Configuration](#configuration) - [Database](#database) @@ -33,6 +33,9 @@ database: # (mandatory) port: 26257 + # The name of the database to use. + name: badaas_db + # The sslmode of the connection to the database server. # (mandatory) sslmode: disable @@ -56,13 +59,13 @@ database: retryTime: 5 ``` -Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mecanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. +Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mechanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. ## Logger -Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. +Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. -Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formated using the Jinja syntax. The values available are `method`, `url` and `protocol`. +Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formatted using the Jinja syntax. The values available are `method`, `url` and `protocol`. ```yml # The settings for the logger. @@ -79,7 +82,7 @@ logger: You can change the host Badaas will bind to, the port and the timeout in seconds. -Additionaly you can change the number of elements returned by default for a paginated response. +Additionally you can change the number of elements returned by default for a paginated response. ```yml # The settings for the http server. @@ -124,7 +127,6 @@ Session are extended if the user made a request to badaas in the "roll duration" Please see the diagram below to see what is the roll duration relative to the session duration. - ```txt | session duration | |<----------------------------------------->| @@ -136,7 +138,7 @@ Please see the diagram below to see what is the roll duration relative to the se ```yml # The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. +# This section contains some good defaults, don't change those value unless you need to. session: # The duration of a user session, in seconds # Default (14400) equal to 4 hours diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go new file mode 100644 index 00000000..51569748 --- /dev/null +++ b/configuration/CommandInitializer.go @@ -0,0 +1,51 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +type CommandInitializer interface { + // Inits VerdeterCommand "command" with the all the keys that are configurable in badaas + Init(command *verdeter.VerdeterCommand) error +} + +// Implementation of the CommandInitializer +type commandInitializerImpl struct { + KeySetter KeySetter + Keys []KeyDefinition +} + +// Constructor of CommandInitializer with the keys for badaas +// it uses the keySetter to set the configuration keys in the VerdeterCommand +func NewCommandInitializer(keySetter KeySetter) CommandInitializer { + keys := []KeyDefinition{ + { + Name: "config_path", + ValType: verdeter.IsStr, + Usage: "Path to the config file/directory", + DefaultV: ".", + }, + } + keys = append(keys, getDatabaseConfigurationKeys()...) + keys = append(keys, getSessionConfigurationKeys()...) + keys = append(keys, getInitializationConfigurationKeys()...) + keys = append(keys, getServerConfigurationKeys()...) + keys = append(keys, getLoggerConfigurationKeys()...) + + return commandInitializerImpl{ + KeySetter: keySetter, + Keys: keys, + } +} + +// Inits VerdeterCommand "cmd" with the all the keys in the Keys of the initializer +func (initializer commandInitializerImpl) Init(command *verdeter.VerdeterCommand) error { + for _, key := range initializer.Keys { + err := initializer.KeySetter.Set(command, key) + if err != nil { + return err + } + } + + return nil +} diff --git a/configuration/CommandInitializer_test.go b/configuration/CommandInitializer_test.go new file mode 100644 index 00000000..bbe3e1b4 --- /dev/null +++ b/configuration/CommandInitializer_test.go @@ -0,0 +1,39 @@ +package configuration_test + +import ( + "errors" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/ditrit/badaas/configuration" + configurationMocks "github.com/ditrit/badaas/mocks/configuration" + "github.com/ditrit/verdeter" +) + +var rootCommand = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "Backend and Distribution as a Service", + Run: doNothing, +}) + +func doNothing(_ *cobra.Command, _ []string) {} + +func TestInitCommandsInitializerSetsAllKeysWithoutError(t *testing.T) { + err := configuration.NewCommandInitializer( + configuration.NewKeySetter(), + ).Init(rootCommand) + assert.Nil(t, err) +} + +func TestInitCommandsInitializerReturnsErrorWhenErrorOnKeySet(t *testing.T) { + mockKeySetter := configurationMocks.NewKeySetter(t) + mockKeySetter.On("Set", mock.Anything, mock.Anything).Return(errors.New("error setting key")) + + commandInitializer := configuration.NewCommandInitializer(mockKeySetter) + + err := commandInitializer.Init(rootCommand) + assert.ErrorContains(t, err, "error setting key") +} diff --git a/configuration/DatabaseConfiguration.go b/configuration/DatabaseConfiguration.go index 293874a1..6a950473 100644 --- a/configuration/DatabaseConfiguration.go +++ b/configuration/DatabaseConfiguration.go @@ -3,6 +3,7 @@ package configuration import ( "time" + "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -100,7 +101,7 @@ func (databaseConfiguration *databaseConfigurationImpl) GetRetry() uint { // Return the waiting time between the database connections in seconds func (databaseConfiguration *databaseConfigurationImpl) GetRetryTime() time.Duration { - return intToSecond(int(databaseConfiguration.retryTime)) + return utils.IntToSecond(int(databaseConfiguration.retryTime)) } // Log the values provided by the configuration holder diff --git a/configuration/DatabaseConfigurationKeys.go b/configuration/DatabaseConfigurationKeys.go new file mode 100644 index 00000000..36c3ed37 --- /dev/null +++ b/configuration/DatabaseConfigurationKeys.go @@ -0,0 +1,58 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of database configuration keys +func getDatabaseConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: DatabasePortKey, + ValType: verdeter.IsInt, + Usage: "The port of the database server", + Required: true, + }, + { + Name: DatabaseHostKey, + ValType: verdeter.IsStr, + Usage: "The host of the database server", + Required: true, + }, + { + Name: DatabaseNameKey, + ValType: verdeter.IsStr, + Usage: "The name of the database to use", + }, + { + Name: DatabaseSslmodeKey, + ValType: verdeter.IsStr, + Usage: "The sslmode to use when connecting to the database server", + Required: true, + }, + { + Name: DatabaseUsernameKey, + ValType: verdeter.IsStr, + Usage: "The username of the account on the database server", + Required: true, + }, + { + Name: DatabasePasswordKey, + ValType: verdeter.IsStr, + Usage: "The password of the account one the database server", + Required: true, + }, + { + Name: DatabaseRetryKey, + ValType: verdeter.IsUint, + Usage: "The number of times badaas tries to establish a connection with the database", + DefaultV: uint(10), + }, + { + Name: DatabaseRetryDurationKey, + ValType: verdeter.IsUint, + Usage: "The duration in seconds badaas wait between connection attempts", + DefaultV: uint(5), + }, + } +} diff --git a/configuration/HttpServerConfiguration.go b/configuration/HttpServerConfiguration.go index 0a00f5f3..a81346de 100644 --- a/configuration/HttpServerConfiguration.go +++ b/configuration/HttpServerConfiguration.go @@ -1,10 +1,13 @@ package configuration import ( + "fmt" "time" "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the http server settings @@ -18,6 +21,7 @@ const ( // Hold the configuration values for the http server type HTTPServerConfiguration interface { ConfigurationHolder + GetAddr() string GetHost() string GetPort() int GetMaxTimeout() time.Duration @@ -41,7 +45,7 @@ func NewHTTPServerConfiguration() HTTPServerConfiguration { func (httpServerConfiguration *hTTPServerConfigurationImpl) Reload() { httpServerConfiguration.host = viper.GetString(ServerHostKey) httpServerConfiguration.port = viper.GetInt(ServerPortKey) - httpServerConfiguration.timeout = intToSecond(viper.GetInt(ServerTimeoutKey)) + httpServerConfiguration.timeout = utils.IntToSecond(viper.GetInt(ServerTimeoutKey)) } // Return the host addr @@ -54,7 +58,7 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) GetPort() int { return httpServerConfiguration.port } -// Return the maximum timout for read and write +// Return the maximum timeout for read and write func (httpServerConfiguration *hTTPServerConfigurationImpl) GetMaxTimeout() time.Duration { return httpServerConfiguration.timeout } @@ -67,3 +71,11 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) Log(logger *zap.Logg zap.Duration("timeout", httpServerConfiguration.timeout), ) } + +// Create the addr string in format: ":" +func (httpServerConfiguration *hTTPServerConfigurationImpl) GetAddr() string { + return fmt.Sprintf("%s:%d", + httpServerConfiguration.GetHost(), + httpServerConfiguration.GetPort(), + ) +} diff --git a/configuration/HttpServerConfiguration_test.go b/configuration/HttpServerConfiguration_test.go index d50119c5..899e91fe 100644 --- a/configuration/HttpServerConfiguration_test.go +++ b/configuration/HttpServerConfiguration_test.go @@ -4,12 +4,13 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var HTTPServerConfigurationString = `server: @@ -27,12 +28,19 @@ func TestHTTPServerConfigurationGetPort(t *testing.T) { HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() assert.Equal(t, 8000, HTTPServerConfiguration.GetPort()) } + func TestHTTPServerConfigurationGetHost(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() assert.Equal(t, "0.0.0.0", HTTPServerConfiguration.GetHost()) } +func TestHTTPServerConfigurationGetAddr(t *testing.T) { + setupViperEnvironment(HTTPServerConfigurationString) + HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0:8000", HTTPServerConfiguration.GetAddr()) +} + func TestHTTPServerConfigurationGetMaxTimeout(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() diff --git a/configuration/InitializationConfigurationKeys.go b/configuration/InitializationConfigurationKeys.go new file mode 100644 index 00000000..7eb896ad --- /dev/null +++ b/configuration/InitializationConfigurationKeys.go @@ -0,0 +1,17 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of initialization configuration keys +func getInitializationConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: InitializationDefaultAdminPasswordKey, + ValType: verdeter.IsStr, + Usage: "Set the default admin password is the admin user is not created yet.", + DefaultV: "admin", + }, + } +} diff --git a/configuration/KeySetter.go b/configuration/KeySetter.go new file mode 100644 index 00000000..d549a014 --- /dev/null +++ b/configuration/KeySetter.go @@ -0,0 +1,47 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/models" +) + +type KeySetter interface { + // Configures the VerdeterCommand "command" with the information contained in "key" + Set(command *verdeter.VerdeterCommand, key KeyDefinition) error +} + +type KeyDefinition struct { + Name string + ValType models.ConfigType + Usage string + Required bool + DefaultV any + Validator *models.Validator +} + +type keySetterImpl struct{} + +func NewKeySetter() KeySetter { + return keySetterImpl{} +} + +// Configures the VerdeterCommand "command" with the information contained in "key" +func (ks keySetterImpl) Set(command *verdeter.VerdeterCommand, key KeyDefinition) error { + if err := command.GKey(key.Name, key.ValType, "", key.Usage); err != nil { + return err + } + + if key.Required { + command.SetRequired(key.Name) + } + + if key.DefaultV != nil { + command.SetDefault(key.Name, key.DefaultV) + } + + if key.Validator != nil { + command.AddValidator(key.Name, *key.Validator) + } + + return nil +} diff --git a/configuration/LoggerConfigurationKeys.go b/configuration/LoggerConfigurationKeys.go new file mode 100644 index 00000000..b5686730 --- /dev/null +++ b/configuration/LoggerConfigurationKeys.go @@ -0,0 +1,26 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +// Definition of logger configuration keys +func getLoggerConfigurationKeys() []KeyDefinition { + modeValidator := validators.AuthorizedValues("prod", "dev") + return []KeyDefinition{ + { + Name: LoggerRequestTemplateKey, + ValType: verdeter.IsStr, + Usage: "Template message for all request logs", + DefaultV: "Receive {{method}} request on {{url}}", + }, + { + Name: LoggerModeKey, + ValType: verdeter.IsStr, + Usage: "The logger mode (default to \"prod\")", + DefaultV: "prod", + Validator: &modeValidator, + }, + } +} diff --git a/configuration/ServerConfigurationKeys.go b/configuration/ServerConfigurationKeys.go new file mode 100644 index 00000000..7582984d --- /dev/null +++ b/configuration/ServerConfigurationKeys.go @@ -0,0 +1,37 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +// Definition of server configuration keys +func getServerConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: ServerTimeoutKey, + ValType: verdeter.IsInt, + Usage: "Maximum timeout of the http server in second (default is 15s)", + DefaultV: 15, + }, + { + Name: ServerHostKey, + ValType: verdeter.IsStr, + Usage: "Address to bind (default is 0.0.0.0)", + DefaultV: "0.0.0.0", + }, + { + Name: ServerPortKey, + ValType: verdeter.IsInt, + Usage: "Port to bind (default is 8000)", + DefaultV: 8000, + Validator: &validators.CheckTCPHighPort, + }, + { + Name: ServerPaginationMaxElemPerPage, + ValType: verdeter.IsUint, + Usage: "The max number of records returned per page", + DefaultV: uint(100), + }, + } +} diff --git a/configuration/SessionConfiguration.go b/configuration/SessionConfiguration.go index 3a76ef6f..1d330a8c 100644 --- a/configuration/SessionConfiguration.go +++ b/configuration/SessionConfiguration.go @@ -3,6 +3,7 @@ package configuration import ( "time" + "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -53,9 +54,9 @@ func (sessionConfiguration *sessionConfigurationImpl) GetRollDuration() time.Dur // Reload session configuration func (sessionConfiguration *sessionConfigurationImpl) Reload() { - sessionConfiguration.sessionDuration = intToSecond(int(viper.GetUint(SessionDurationKey))) - sessionConfiguration.pullInterval = intToSecond(int(viper.GetUint(SessionPullIntervalKey))) - sessionConfiguration.rollDuration = intToSecond(int(viper.GetUint(SessionRollIntervalKey))) + sessionConfiguration.sessionDuration = utils.IntToSecond(int(viper.GetUint(SessionDurationKey))) + sessionConfiguration.pullInterval = utils.IntToSecond(int(viper.GetUint(SessionPullIntervalKey))) + sessionConfiguration.rollDuration = utils.IntToSecond(int(viper.GetUint(SessionRollIntervalKey))) } // Log the values provided by the configuration holder diff --git a/configuration/SessionConfigurationKeys.go b/configuration/SessionConfigurationKeys.go new file mode 100644 index 00000000..4a0960f7 --- /dev/null +++ b/configuration/SessionConfigurationKeys.go @@ -0,0 +1,29 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of session configuration keys +func getSessionConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: SessionDurationKey, + ValType: verdeter.IsUint, + Usage: "The duration of a user session in seconds", + DefaultV: uint(3600 * 4), // 4 hours by default + }, + { + Name: SessionPullIntervalKey, + ValType: verdeter.IsUint, + Usage: "The refresh interval in seconds. Badaas refresh it's internal session cache periodically", + DefaultV: uint(30), // 30 seconds by default + }, + { + Name: SessionRollIntervalKey, + ValType: verdeter.IsUint, + Usage: "The interval in which the user can renew it's session by making a request", + DefaultV: uint(3600), // 1 hour by default + }, + } +} diff --git a/controllers/ModuleFx.go b/controllers/ModuleFx.go deleted file mode 100644 index ccab54eb..00000000 --- a/controllers/ModuleFx.go +++ /dev/null @@ -1,10 +0,0 @@ -package controllers - -import "go.uber.org/fx" - -// ControllerModule for fx -var ControllerModule = fx.Module( - "controllers", - fx.Provide(NewInfoController), - fx.Provide(NewBasicAuthentificationController), -) diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index d98c4de5..7d9205fb 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -2,48 +2,50 @@ package controllers import ( "encoding/json" + "errors" + "fmt" "net/http" + "time" + + "go.uber.org/zap" "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" ) -var ( - // Sent when the request is malformed - HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( - http.StatusBadRequest, - "Request malformed", - "The schema of the received data is not correct", - nil, - false) +// HTTPErrRequestMalformed is sent when the request is malformed +var HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( + http.StatusBadRequest, + "Request malformed", + "The schema of the received data is not correct", + nil, + false, ) -// Basic Authentification Controller -type BasicAuthentificationController interface { +type BasicAuthenticationController interface { BasicLoginHandler(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) Logout(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) } // Check interface compliance -var _ BasicAuthentificationController = (*basicAuthentificationController)(nil) +var _ BasicAuthenticationController = (*basicAuthenticationController)(nil) -// BasicAuthentificationController implementation -type basicAuthentificationController struct { +type basicAuthenticationController struct { logger *zap.Logger userService userservice.UserService sessionService sessionservice.SessionService } -// BasicAuthentificationController contructor -func NewBasicAuthentificationController( +// BasicAuthenticationController constructor +func NewBasicAuthenticationController( logger *zap.Logger, userService userservice.UserService, sessionService sessionservice.SessionService, -) BasicAuthentificationController { - return &basicAuthentificationController{ +) BasicAuthenticationController { + return &basicAuthenticationController{ logger: logger, userService: userService, sessionService: sessionService, @@ -51,22 +53,38 @@ func NewBasicAuthentificationController( } // Log In with username and password -func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { var loginJSONStruct dto.UserLoginDTO err := json.NewDecoder(r.Body).Decode(&loginJSONStruct) if err != nil { return nil, HTTPErrRequestMalformed } - user, herr := basicAuthController.userService.GetUser(loginJSONStruct) - if herr != nil { - return nil, herr + + user, err := basicAuthController.userService.GetUser(loginJSONStruct) + if err != nil { + if errors.Is(err, orm.ErrObjectNotFound) { + return nil, httperrors.NewErrorNotFound( + "user", + fmt.Sprintf("no user found with email %q", loginJSONStruct.Email), + ) + } else if errors.Is(err, userservice.ErrWrongPassword) { + return nil, httperrors.NewUnauthorizedError( + "wrong password", "the provided password is incorrect", + ) + } + + return nil, httperrors.NewDBError(err) } // On valid password, generate a session and return it's uuid to the client - herr = basicAuthController.sessionService.LogUserIn(user, w) + session, err := basicAuthController.sessionService.LogUserIn(user) + if err != nil { + return nil, httperrors.NewDBError(err) + } + + herr := createAndSetAccessTokenCookie(w, session.ID.String()) if herr != nil { return nil, herr - } return dto.DTOLoginSuccess{ @@ -77,7 +95,35 @@ func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w } // Log Out the user -func (basicAuthController *basicAuthentificationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { - basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context()), w) +func (basicAuthController *basicAuthenticationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + herr := basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context())) + if herr != nil { + return nil, herr + } + + herr = createAndSetAccessTokenCookie(w, "") + if herr != nil { + return nil, herr + } + return nil, nil } + +func createAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) httperrors.HTTPError { + accessToken := &http.Cookie{ + Name: "access_token", + Path: "/", + Value: sessionUUID, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod + Secure: false, // TODO change to true in prod + Expires: time.Now().Add(48 * time.Hour), + } + err := accessToken.Valid() + if err != nil { + return httperrors.NewInternalServerError("access token error", "unable to create access token", err) + } + + http.SetCookie(w, accessToken) + return nil +} diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 2fecf795..254bc011 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -4,17 +4,19 @@ import ( "net/http/httptest" "strings" "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/httperrors" mocksSessionService "github.com/ditrit/badaas/mocks/services/sessionservice" mocksUserService "github.com/ditrit/badaas/mocks/services/userservice" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" - "go.uber.org/zap/zaptest/observer" ) func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { @@ -24,7 +26,7 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { userService := mocksUserService.NewUserService(t) sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -32,14 +34,13 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader("qsdqsdqsd"), ) payload, err := controller.BasicLoginHandler(response, request) assert.Equal(t, controllers.HTTPErrRequestMalformed, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_UserNotFound(t *testing.T) { @@ -55,7 +56,7 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { Return(nil, httperrors.AnError) sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -63,7 +64,7 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -73,7 +74,6 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginFailed(t *testing.T) { @@ -86,7 +86,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -94,7 +94,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{}, + UUIDModel: orm.UUIDModel{}, Username: "bob", Email: "bob@email.com", Password: []byte("hash of 1234"), @@ -104,10 +104,10 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { Return(user, nil) sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(httperrors.AnError) + On("LogUserIn", user). + Return(nil, httperrors.AnError) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -116,7 +116,6 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { @@ -129,7 +128,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -137,8 +136,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, Username: "bob", Email: "bob@email.com", @@ -149,10 +148,10 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { Return(user, nil) sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(nil) + On("LogUserIn", user). + Return(models.NewSession(user.ID, time.Duration(5)), nil) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, diff --git a/controllers/info.go b/controllers/info.go index f39f30a2..50e58ad3 100644 --- a/controllers/info.go +++ b/controllers/info.go @@ -3,34 +3,40 @@ package controllers import ( "net/http" + "github.com/Masterminds/semver/v3" "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/resources" ) // The information controller type InformationController interface { - // Return the badaas server informations + // Return the badaas server information Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) } // check interface compliance var _ InformationController = (*infoControllerImpl)(nil) -// The InformationController constructor -func NewInfoController() InformationController { - return &infoControllerImpl{} -} - // The concrete implementation of the InformationController -type infoControllerImpl struct{} +type infoControllerImpl struct { + Version *semver.Version +} -// Return the badaas server informations -func (*infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +// The InformationController constructor +func NewInfoController(version *semver.Version) InformationController { + return &infoControllerImpl{ + Version: version, + } +} - infos := &dto.DTOBadaasServerInfo{ +// Return the badaas server information +func (c *infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return &BadaasServerInfo{ Status: "OK", - Version: resources.Version, - } - return infos, nil + Version: c.Version.String(), + }, nil +} + +type BadaasServerInfo struct { + Status string `json:"status"` + Version string `json:"version"` } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e616ca88..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3.5' - -services: - db: - image: cockroachdb/cockroach:latest - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - command: start-single-node --insecure - volumes: - - "${PWD}/_temp/cockroach-data/crdb:/cockroach/cockroach-data" - - - api: - build: ditrit/badaas:latest # local image - ports: - - "8000:8000" - depends_on: - - db diff --git a/docker/test_api/Dockerfile b/docker/test_api/Dockerfile new file mode 100644 index 00000000..fbeba3a2 --- /dev/null +++ b/docker/test_api/Dockerfile @@ -0,0 +1,10 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +FROM golang:1.19-alpine +RUN addgroup -S badaas \ + && adduser -S badaas -G badaas +USER badaas +WORKDIR /badaas +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 +ENV GOFLAGS=-buildvcs=false \ No newline at end of file diff --git a/scripts/e2e/api/badaas.yml b/docker/test_api/badaas.yml similarity index 85% rename from scripts/e2e/api/badaas.yml rename to docker/test_api/badaas.yml index 831b12d8..71c3ab7e 100644 --- a/scripts/e2e/api/badaas.yml +++ b/docker/test_api/badaas.yml @@ -3,16 +3,15 @@ server: host: "0.0.0.0" # listening on all interfaces timeout: 15 # in seconds pagination: - page: + page: max: 10 - database: - host: e2e-db-1 + host: badaas-test-db port: 26257 sslmode: disable username: root - password: postres + password: postgres name: badaas_db init: retry: 10 diff --git a/docker/test_api/docker-compose.yml b/docker/test_api/docker-compose.yml new file mode 100644 index 00000000..6796447c --- /dev/null +++ b/docker/test_api/docker-compose.yml @@ -0,0 +1,19 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + api: + container_name: "badaas-test-api" + build: + context: ../.. + dockerfile: ./docker/test_api/Dockerfile + image: badaas-test-api + volumes: + - ../..:/badaas:ro + entrypoint: go run /badaas/test_e2e/test_api.go --config_path /badaas/docker/test_api/badaas.yml + ports: + - "8000:8000" + restart: always + depends_on: + db: + condition: service_healthy diff --git a/docker/test_db/docker-compose.yml b/docker/test_db/docker-compose.yml new file mode 100644 index 00000000..2a071c16 --- /dev/null +++ b/docker/test_db/docker-compose.yml @@ -0,0 +1,22 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + db: + container_name: "badaas-test-db" + image: cockroachdb/cockroach:latest + volumes: + - .:/cockroach/files + working_dir: /cockroach + entrypoint: /cockroach/cockroach.sh start-single-node --insecure --log-config-file=files/logs.yaml + ports: + - "26257:26257" + - "8080:8080" # Web based dashboard + environment: + - COCKROACH_USER=root + - COCKROACH_DATABASE=badaas_db + healthcheck: + test: curl --fail http://localhost:8080 || exit 1 + interval: 10s + timeout: 5s + retries: 5 diff --git a/scripts/e2e/db/logs.yaml b/docker/test_db/logs.yaml similarity index 100% rename from scripts/e2e/db/logs.yaml rename to docker/test_db/logs.yaml diff --git a/docker/wait_for_api.sh b/docker/wait_for_api.sh new file mode 100755 index 00000000..9c586dde --- /dev/null +++ b/docker/wait_for_api.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until $(curl --output /dev/null --silent --fail http://localhost:$1); do + printf '.' + sleep 5 +done \ No newline at end of file diff --git a/docker/wait_for_db.sh b/docker/wait_for_db.sh new file mode 100755 index 00000000..04263a18 --- /dev/null +++ b/docker/wait_for_db.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until [ "`docker inspect -f {{.State.Health.Status}} badaas-test-db`"=="healthy" ]; do + printf '.'; + sleep 1; +done; \ No newline at end of file diff --git a/features/api_info.feature b/features/api_info.feature deleted file mode 100644 index f7b6790f..00000000 --- a/features/api_info.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Test info controller - -Scenario: Server should return ok and current project version - When I request "/info" - Then I expect status code is "200" - And I expect response field "status" is "OK" - And I expect response field "version" is "UNRELEASED" diff --git a/go.mod b/go.mod index 788ae80b..2391c154 100644 --- a/go.mod +++ b/go.mod @@ -3,66 +3,60 @@ module github.com/ditrit/badaas go 1.18 require ( - github.com/Masterminds/squirrel v1.5.3 - github.com/cucumber/godog v0.12.5 + github.com/Masterminds/semver/v3 v3.1.1 github.com/ditrit/verdeter v0.4.0 + github.com/elliotchance/pie/v2 v2.7.0 github.com/google/uuid v1.3.0 + github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/jackc/pgconn v1.13.0 - github.com/magiconair/properties v1.8.6 + github.com/jackc/pgconn v1.14.0 + github.com/magiconair/properties v1.8.7 github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 - github.com/spf13/cobra v1.5.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.8.1 - go.uber.org/fx v1.18.2 - go.uber.org/zap v1.23.0 - golang.org/x/crypto v0.1.0 - gorm.io/driver/postgres v1.4.5 - gorm.io/gorm v1.24.1 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/fx v1.19.3 + go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.9.0 + gorm.io/driver/postgres v1.5.2 + gorm.io/gorm v1.25.1 + gotest.tools v2.2.0+incompatible ) require ( - github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect - github.com/cucumber/messages-go/v16 v16.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/goph/emperror v0.17.2 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-memdb v1.3.3 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.12.0 // indirect - github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.15.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a7b58dea..d87cc7d2 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -41,28 +40,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -70,64 +56,39 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= -github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= -github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= -github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= -github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= -github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= -github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd h1:r2NABj0IHkzEtp4ZGaKpWNxsWAhjQU4ezxyELvXObrk= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= +github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -163,7 +124,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -187,51 +149,19 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= -github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= -github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/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/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -241,9 +171,8 @@ github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -259,105 +188,63 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -365,63 +252,34 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -435,21 +293,20 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -458,29 +315,21 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= -go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= -go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -492,10 +341,11 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -506,6 +356,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -529,13 +381,10 @@ 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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= @@ -567,6 +416,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -586,13 +437,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -610,7 +458,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -633,13 +480,18 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -648,19 +500,18 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -672,9 +523,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -682,7 +530,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -708,6 +555,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -802,36 +650,26 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= -gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= -gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work b/go.work new file mode 100644 index 00000000..241c23dd --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.18 + +use ( + . + ./test_e2e +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..c2bf63ee --- /dev/null +++ b/go.work.sum @@ -0,0 +1,341 @@ +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/httperrors/httperrors.go b/httperrors/httperrors.go index c5a02ecf..0a09fb2b 100644 --- a/httperrors/httperrors.go +++ b/httperrors/httperrors.go @@ -5,23 +5,22 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/persistence/models/dto" "go.uber.org/zap" -) -var ( - // AnError is an HTTPError instance useful for testing. If the code does not care - // about HTTPError specifics, and only needs to return the HTTPError for example, this - // HTTPError should be used to make the test code more readable. - AnError HTTPError = &HTTPErrorImpl{ - Status: -1, - Err: "TESTING ERROR", - Message: "USE ONLY FOR TESTING", - GolangError: nil, - toLog: true, - } + "github.com/ditrit/badaas/persistence/models/dto" ) +// AnError is an HTTPError instance useful for testing. If the code does not care +// about HTTPError specifics, and only needs to return the HTTPError for example, this +// HTTPError should be used to make the test code more readable. +var AnError HTTPError = &HTTPErrorImpl{ + Status: -1, + Err: "TESTING ERROR", + Message: "USE ONLY FOR TESTING", + GolangError: nil, + toLog: true, +} + type HTTPError interface { error @@ -93,18 +92,18 @@ func NewHTTPError(status int, err string, message string, golangError error, toL } } -// A contructor for an HttpError "Not Found" -func NewErrorNotFound(ressourceName string, msg string) HTTPError { +// A constructor for an HttpError "Not Found" +func NewErrorNotFound(resourceName string, msg string) HTTPError { return NewHTTPError( http.StatusNotFound, - fmt.Sprintf("%s not found", ressourceName), + fmt.Sprintf("%s not found", resourceName), msg, nil, false, ) } -// A contructor for an HttpError "Internal Server Error" +// A constructor for an HttpError "Internal Server Error" func NewInternalServerError(errorName string, msg string, err error) HTTPError { return NewHTTPError( http.StatusInternalServerError, @@ -115,7 +114,12 @@ func NewInternalServerError(errorName string, msg string, err error) HTTPError { ) } -// A contructor for an HttpError "Unauthorized Error" +// Constructor for an HttpError "DB Error", a internal server error produced by a query +func NewDBError(err error) HTTPError { + return NewInternalServerError("db error", "database query failed", err) +} + +// A constructor for an HttpError "Unauthorized Error" func NewUnauthorizedError(errorName string, msg string) HTTPError { return NewHTTPError( http.StatusUnauthorized, diff --git a/mocks/configuration/CommandInitializer.go b/mocks/configuration/CommandInitializer.go new file mode 100644 index 00000000..14b1686b --- /dev/null +++ b/mocks/configuration/CommandInitializer.go @@ -0,0 +1,42 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + verdeter "github.com/ditrit/verdeter" + mock "github.com/stretchr/testify/mock" +) + +// CommandInitializer is an autogenerated mock type for the CommandInitializer type +type CommandInitializer struct { + mock.Mock +} + +// Init provides a mock function with given fields: command +func (_m *CommandInitializer) Init(command *verdeter.VerdeterCommand) error { + ret := _m.Called(command) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand) error); ok { + r0 = rf(command) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCommandInitializer interface { + mock.TestingT + Cleanup(func()) +} + +// NewCommandInitializer creates a new instance of CommandInitializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCommandInitializer(t mockConstructorTestingTNewCommandInitializer) *CommandInitializer { + mock := &CommandInitializer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/ConfigurationHolder.go b/mocks/configuration/ConfigurationHolder.go index f6475bb1..4a047153 100644 --- a/mocks/configuration/ConfigurationHolder.go +++ b/mocks/configuration/ConfigurationHolder.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/DatabaseConfiguration.go b/mocks/configuration/DatabaseConfiguration.go index a210f186..51f0f487 100644 --- a/mocks/configuration/DatabaseConfiguration.go +++ b/mocks/configuration/DatabaseConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/HTTPServerConfiguration.go b/mocks/configuration/HTTPServerConfiguration.go index 77cb41e5..ffb21116 100644 --- a/mocks/configuration/HTTPServerConfiguration.go +++ b/mocks/configuration/HTTPServerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -15,6 +15,20 @@ type HTTPServerConfiguration struct { mock.Mock } +// GetAddr provides a mock function with given fields: +func (_m *HTTPServerConfiguration) GetAddr() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // GetHost provides a mock function with given fields: func (_m *HTTPServerConfiguration) GetHost() string { ret := _m.Called() diff --git a/mocks/configuration/InitializationConfiguration.go b/mocks/configuration/InitializationConfiguration.go index 58bdec3b..303c62b7 100644 --- a/mocks/configuration/InitializationConfiguration.go +++ b/mocks/configuration/InitializationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/KeySetter.go b/mocks/configuration/KeySetter.go new file mode 100644 index 00000000..05885cc0 --- /dev/null +++ b/mocks/configuration/KeySetter.go @@ -0,0 +1,44 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + configuration "github.com/ditrit/badaas/configuration" + mock "github.com/stretchr/testify/mock" + + verdeter "github.com/ditrit/verdeter" +) + +// KeySetter is an autogenerated mock type for the KeySetter type +type KeySetter struct { + mock.Mock +} + +// Set provides a mock function with given fields: command, key +func (_m *KeySetter) Set(command *verdeter.VerdeterCommand, key configuration.KeyDefinition) error { + ret := _m.Called(command, key) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand, configuration.KeyDefinition) error); ok { + r0 = rf(command, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewKeySetter interface { + mock.TestingT + Cleanup(func()) +} + +// NewKeySetter creates a new instance of KeySetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewKeySetter(t mockConstructorTestingTNewKeySetter) *KeySetter { + mock := &KeySetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/LoggerConfiguration.go b/mocks/configuration/LoggerConfiguration.go index 2115ec7e..88682c1c 100644 --- a/mocks/configuration/LoggerConfiguration.go +++ b/mocks/configuration/LoggerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/PaginationConfiguration.go b/mocks/configuration/PaginationConfiguration.go index 9146b875..7db54091 100644 --- a/mocks/configuration/PaginationConfiguration.go +++ b/mocks/configuration/PaginationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/SessionConfiguration.go b/mocks/configuration/SessionConfiguration.go index 2d5e9ee7..e107947d 100644 --- a/mocks/configuration/SessionConfiguration.go +++ b/mocks/configuration/SessionConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/controllers/BasicAuthentificationController.go b/mocks/controllers/BasicAuthenticationController.go similarity index 53% rename from mocks/controllers/BasicAuthentificationController.go rename to mocks/controllers/BasicAuthenticationController.go index 19092bfa..5bf3c0b9 100644 --- a/mocks/controllers/BasicAuthentificationController.go +++ b/mocks/controllers/BasicAuthenticationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -9,16 +9,20 @@ import ( mock "github.com/stretchr/testify/mock" ) -// BasicAuthentificationController is an autogenerated mock type for the BasicAuthentificationController type -type BasicAuthentificationController struct { +// BasicAuthenticationController is an autogenerated mock type for the BasicAuthenticationController type +type BasicAuthenticationController struct { mock.Mock } // BasicLoginHandler provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -27,7 +31,6 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -40,10 +43,14 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } // Logout provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -52,7 +59,6 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -64,14 +70,14 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * return r0, r1 } -type mockConstructorTestingTNewBasicAuthentificationController interface { +type mockConstructorTestingTNewBasicAuthenticationController interface { mock.TestingT Cleanup(func()) } -// NewBasicAuthentificationController creates a new instance of BasicAuthentificationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBasicAuthentificationController(t mockConstructorTestingTNewBasicAuthentificationController) *BasicAuthentificationController { - mock := &BasicAuthentificationController{} +// NewBasicAuthenticationController creates a new instance of BasicAuthenticationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBasicAuthenticationController(t mockConstructorTestingTNewBasicAuthenticationController) *BasicAuthenticationController { + mock := &BasicAuthenticationController{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/mocks/controllers/InformationController.go b/mocks/controllers/InformationController.go index 51eeffef..4a82f694 100644 --- a/mocks/controllers/InformationController.go +++ b/mocks/controllers/InformationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -19,6 +19,10 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ ret := _m.Called(response, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(response, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(response, r) } else { @@ -27,7 +31,6 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(response, r) } else { diff --git a/mocks/httperrors/HTTPError.go b/mocks/httperrors/HTTPError.go index cbe71e52..3bae2ab3 100644 --- a/mocks/httperrors/HTTPError.go +++ b/mocks/httperrors/HTTPError.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/orm/BadaasID.go b/mocks/orm/BadaasID.go new file mode 100644 index 00000000..cf2c0098 --- /dev/null +++ b/mocks/orm/BadaasID.go @@ -0,0 +1,25 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// BadaasID is an autogenerated mock type for the BadaasID type +type BadaasID struct { + mock.Mock +} + +type mockConstructorTestingTNewBadaasID interface { + mock.TestingT + Cleanup(func()) +} + +// NewBadaasID creates a new instance of BadaasID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBadaasID(t mockConstructorTestingTNewBadaasID) *BadaasID { + mock := &BadaasID{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/CRUDRepository.go b/mocks/orm/CRUDRepository.go new file mode 100644 index 00000000..db93a68f --- /dev/null +++ b/mocks/orm/CRUDRepository.go @@ -0,0 +1,163 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// CRUDRepository is an autogenerated mock type for the CRUDRepository type +type CRUDRepository[T interface{}, ID orm.BadaasID] struct { + mock.Mock +} + +// Create provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Create(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Delete(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetByID provides a mock function with given fields: tx, id +func (_m *CRUDRepository[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + ret := _m.Called(tx, id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) (*T, error)); ok { + return rf(tx, id) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) *T); ok { + r0 = rf(tx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ID) error); ok { + r1 = rf(tx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) ([]*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) []*T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...orm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) (*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) *T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Save provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Save(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCRUDRepository interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDRepository[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { + mock := &CRUDRepository[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/CRUDService.go b/mocks/orm/CRUDService.go new file mode 100644 index 00000000..1a047339 --- /dev/null +++ b/mocks/orm/CRUDService.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" +) + +// CRUDService is an autogenerated mock type for the CRUDService type +type CRUDService[T interface{}, ID orm.BadaasID] struct { + mock.Mock +} + +// GetByID provides a mock function with given fields: id +func (_m *CRUDService[T, ID]) GetByID(id ID) (*T, error) { + ret := _m.Called(id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(ID) (*T, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(ID) *T); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(ID) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) ([]*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) []*T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) QueryOne(conditions ...orm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) (*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) *T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDService interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDService[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { + mock := &CRUDService[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/Condition.go b/mocks/orm/Condition.go new file mode 100644 index 00000000..2d214a05 --- /dev/null +++ b/mocks/orm/Condition.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// Condition is an autogenerated mock type for the Condition type +type Condition[T interface{}] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, tableName +func (_m *Condition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + ret := _m.Called(query, tableName) + + var r0 *gorm.DB + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { + r0 = rf(query, tableName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { + r1 = rf(query, tableName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// interfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Condition[T]) interfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewCondition creates a new instance of Condition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCondition[T interface{}](t mockConstructorTestingTNewCondition) *Condition[T] { + mock := &Condition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/Operator.go b/mocks/orm/Operator.go new file mode 100644 index 00000000..e2ae8183 --- /dev/null +++ b/mocks/orm/Operator.go @@ -0,0 +1,63 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Operator is an autogenerated mock type for the Operator type +type Operator[T interface{}] struct { + mock.Mock +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Operator[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// ToSQL provides a mock function with given fields: columnName +func (_m *Operator[T]) ToSQL(columnName string) (string, []interface{}, error) { + ret := _m.Called(columnName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(string) (string, []interface{}, error)); ok { + return rf(columnName) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(columnName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) []interface{}); ok { + r1 = rf(columnName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(string) error); ok { + r2 = rf(columnName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewOperator interface { + mock.TestingT + Cleanup(func()) +} + +// NewOperator creates a new instance of Operator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewOperator[T interface{}](t mockConstructorTestingTNewOperator) *Operator[T] { + mock := &Operator[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/WhereCondition.go b/mocks/orm/WhereCondition.go new file mode 100644 index 00000000..725d3ef3 --- /dev/null +++ b/mocks/orm/WhereCondition.go @@ -0,0 +1,106 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// WhereCondition is an autogenerated mock type for the WhereCondition type +type WhereCondition[T interface{}] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, tableName +func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + ret := _m.Called(query, tableName) + + var r0 *gorm.DB + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { + r0 = rf(query, tableName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { + r1 = rf(query, tableName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSQL provides a mock function with given fields: query, tableName +func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []interface{}, error) { + ret := _m.Called(query, tableName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (string, []interface{}, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) string); ok { + r0 = rf(query, tableName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) []interface{}); ok { + r1 = rf(query, tableName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*gorm.DB, string) error); ok { + r2 = rf(query, tableName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// affectsDeletedAt provides a mock function with given fields: +func (_m *WhereCondition[T]) affectsDeletedAt() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// interfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *WhereCondition[T]) interfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewWhereCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewWhereCondition creates a new instance of WhereCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewWhereCondition[T interface{}](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { + mock := &WhereCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/persistence/models/Tabler.go b/mocks/persistence/models/Tabler.go deleted file mode 100644 index cbc8e129..00000000 --- a/mocks/persistence/models/Tabler.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Tabler is an autogenerated mock type for the Tabler type -type Tabler struct { - mock.Mock -} - -// TableName provides a mock function with given fields: -func (_m *Tabler) TableName() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -type mockConstructorTestingTNewTabler interface { - mock.TestingT - Cleanup(func()) -} - -// NewTabler creates a new instance of Tabler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewTabler(t mockConstructorTestingTNewTabler) *Tabler { - mock := &Tabler{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/pagination/Paginator.go b/mocks/persistence/pagination/Paginator.go deleted file mode 100644 index dabbbf23..00000000 --- a/mocks/persistence/pagination/Paginator.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Paginator is an autogenerated mock type for the Paginator type -type Paginator struct { - mock.Mock -} - -// Limit provides a mock function with given fields: -func (_m *Paginator) Limit() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -// Offset provides a mock function with given fields: -func (_m *Paginator) Offset() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -type mockConstructorTestingTNewPaginator interface { - mock.TestingT - Cleanup(func()) -} - -// NewPaginator creates a new instance of Paginator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPaginator(t mockConstructorTestingTNewPaginator) *Paginator { - mock := &Paginator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/CRUDRepository.go b/mocks/persistence/repository/CRUDRepository.go deleted file mode 100644 index 31f127b1..00000000 --- a/mocks/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,207 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - httperrors "github.com/ditrit/badaas/httperrors" - mock "github.com/stretchr/testify/mock" - - models "github.com/ditrit/badaas/persistence/models" - - pagination "github.com/ditrit/badaas/persistence/pagination" - - repository "github.com/ditrit/badaas/persistence/repository" - - squirrel "github.com/Masterminds/squirrel" -) - -// CRUDRepository is an autogenerated mock type for the CRUDRepository type -type CRUDRepository[T models.Tabler, ID interface{}] struct { - mock.Mock -} - -// Count provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Count(_a0 squirrel.Sqlizer) (uint, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 uint - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) uint); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(uint) - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Create provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Create(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Delete provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Delete(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Find provides a mock function with given fields: _a0, _a1, _a2 -func (_m *CRUDRepository[T, ID]) Find(_a0 squirrel.Sqlizer, _a1 pagination.Paginator, _a2 repository.SortOption) (*pagination.Page[T], httperrors.HTTPError) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *pagination.Page[T] - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) *pagination.Page[T]); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*pagination.Page[T]) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0, _a1, _a2) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetAll provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetAll(_a0 repository.SortOption) ([]*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 []*T - if rf, ok := ret.Get(0).(func(repository.SortOption) []*T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*T) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetByID provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetByID(_a0 ID) (*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 *T - if rf, ok := ret.Get(0).(func(ID) *T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*T) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(ID) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Save provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Save(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Transaction provides a mock function with given fields: fn -func (_m *CRUDRepository[T, ID]) Transaction(fn func(repository.CRUDRepository[T, ID]) (interface{}, error)) (interface{}, httperrors.HTTPError) { - ret := _m.Called(fn) - - var r0 interface{} - if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) interface{}); ok { - r0 = rf(fn) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) httperrors.HTTPError); ok { - r1 = rf(fn) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -type mockConstructorTestingTNewCRUDRepository interface { - mock.TestingT - Cleanup(func()) -} - -// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDRepository[T models.Tabler, ID interface{}](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { - mock := &CRUDRepository[T, ID]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/SortOption.go b/mocks/persistence/repository/SortOption.go deleted file mode 100644 index d2e10e83..00000000 --- a/mocks/persistence/repository/SortOption.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// SortOption is an autogenerated mock type for the SortOption type -type SortOption struct { - mock.Mock -} - -// Column provides a mock function with given fields: -func (_m *SortOption) Column() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Desc provides a mock function with given fields: -func (_m *SortOption) Desc() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -type mockConstructorTestingTNewSortOption interface { - mock.TestingT - Cleanup(func()) -} - -// NewSortOption creates a new instance of SortOption. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSortOption(t mockConstructorTestingTNewSortOption) *SortOption { - mock := &SortOption{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/router/middlewares/AuthenticationMiddleware.go b/mocks/router/middlewares/AuthenticationMiddleware.go index a0a4053f..77b8fe2d 100644 --- a/mocks/router/middlewares/AuthenticationMiddleware.go +++ b/mocks/router/middlewares/AuthenticationMiddleware.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONController.go b/mocks/router/middlewares/JSONController.go index b1ad06cb..5fe7aa5a 100644 --- a/mocks/router/middlewares/JSONController.go +++ b/mocks/router/middlewares/JSONController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONHandler.go b/mocks/router/middlewares/JSONHandler.go index 98dd567b..37a072cb 100644 --- a/mocks/router/middlewares/JSONHandler.go +++ b/mocks/router/middlewares/JSONHandler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -20,6 +20,10 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac ret := _m.Called(w, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(w, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(w, r) } else { @@ -28,7 +32,6 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(w, r) } else { diff --git a/mocks/router/middlewares/MiddlewareLogger.go b/mocks/router/middlewares/MiddlewareLogger.go index 671b8ca8..f18920a8 100644 --- a/mocks/router/middlewares/MiddlewareLogger.go +++ b/mocks/router/middlewares/MiddlewareLogger.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index 22b49694..fdf023f2 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -1,18 +1,16 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks import ( - http "net/http" - httperrors "github.com/ditrit/badaas/httperrors" mock "github.com/stretchr/testify/mock" models "github.com/ditrit/badaas/persistence/models" - sessionservice "github.com/ditrit/badaas/services/sessionservice" + orm "github.com/ditrit/badaas/orm" - uuid "github.com/google/uuid" + sessionservice "github.com/ditrit/badaas/services/sessionservice" ) // SessionService is an autogenerated mock type for the SessionService type @@ -21,18 +19,21 @@ type SessionService struct { } // IsValid provides a mock function with given fields: sessionUUID -func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice.SessionClaims) { +func (_m *SessionService) IsValid(sessionUUID orm.UUID) (bool, *sessionservice.SessionClaims) { ret := _m.Called(sessionUUID) var r0 bool - if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok { + var r1 *sessionservice.SessionClaims + if rf, ok := ret.Get(0).(func(orm.UUID) (bool, *sessionservice.SessionClaims)); ok { + return rf(sessionUUID) + } + if rf, ok := ret.Get(0).(func(orm.UUID) bool); ok { r0 = rf(sessionUUID) } else { r0 = ret.Get(0).(bool) } - var r1 *sessionservice.SessionClaims - if rf, ok := ret.Get(1).(func(uuid.UUID) *sessionservice.SessionClaims); ok { + if rf, ok := ret.Get(1).(func(orm.UUID) *sessionservice.SessionClaims); ok { r1 = rf(sessionUUID) } else { if ret.Get(1) != nil { @@ -43,29 +44,39 @@ func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice. return r0, r1 } -// LogUserIn provides a mock function with given fields: user, response -func (_m *SessionService) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(user, response) +// LogUserIn provides a mock function with given fields: user +func (_m *SessionService) LogUserIn(user *models.User) (*models.Session, error) { + ret := _m.Called(user) - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*models.User, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(user, response) + var r0 *models.Session + var r1 error + if rf, ok := ret.Get(0).(func(*models.User) (*models.Session, error)); ok { + return rf(user) + } + if rf, ok := ret.Get(0).(func(*models.User) *models.Session); ok { + r0 = rf(user) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) + r0 = ret.Get(0).(*models.Session) } } - return r0 + if rf, ok := ret.Get(1).(func(*models.User) error); ok { + r1 = rf(user) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// LogUserOut provides a mock function with given fields: sessionClaims, response -func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(sessionClaims, response) +// LogUserOut provides a mock function with given fields: sessionClaims +func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims) httperrors.HTTPError { + ret := _m.Called(sessionClaims) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(sessionClaims, response) + if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims) httperrors.HTTPError); ok { + r0 = rf(sessionClaims) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(httperrors.HTTPError) @@ -76,11 +87,11 @@ func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims } // RollSession provides a mock function with given fields: _a0 -func (_m *SessionService) RollSession(_a0 uuid.UUID) httperrors.HTTPError { +func (_m *SessionService) RollSession(_a0 orm.UUID) httperrors.HTTPError { ret := _m.Called(_a0) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(uuid.UUID) httperrors.HTTPError); ok { + if rf, ok := ret.Get(0).(func(orm.UUID) httperrors.HTTPError); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { diff --git a/mocks/services/userservice/UserService.go b/mocks/services/userservice/UserService.go index 6ce9b7fa..6ecc1e6b 100644 --- a/mocks/services/userservice/UserService.go +++ b/mocks/services/userservice/UserService.go @@ -1,11 +1,9 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks import ( - httperrors "github.com/ditrit/badaas/httperrors" dto "github.com/ditrit/badaas/persistence/models/dto" - mock "github.com/stretchr/testify/mock" models "github.com/ditrit/badaas/persistence/models" @@ -17,10 +15,14 @@ type UserService struct { } // GetUser provides a mock function with given fields: _a0 -func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { +func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, error) { ret := _m.Called(_a0) var r0 *models.User + var r1 error + if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) (*models.User, error)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) *models.User); ok { r0 = rf(_a0) } else { @@ -29,13 +31,10 @@ func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.H } } - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) httperrors.HTTPError); ok { + if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) error); ok { r1 = rf(_a0) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } + r1 = ret.Error(1) } return r0, r1 @@ -46,6 +45,10 @@ func (_m *UserService) NewUser(username string, email string, password string) ( ret := _m.Called(username, email, password) var r0 *models.User + var r1 error + if rf, ok := ret.Get(0).(func(string, string, string) (*models.User, error)); ok { + return rf(username, email, password) + } if rf, ok := ret.Get(0).(func(string, string, string) *models.User); ok { r0 = rf(username, email, password) } else { @@ -54,7 +57,6 @@ func (_m *UserService) NewUser(username string, email string, password string) ( } } - var r1 error if rf, ok := ret.Get(1).(func(string, string, string) error); ok { r1 = rf(username, email, password) } else { diff --git a/commands/init.go b/modules.go similarity index 53% rename from commands/init.go rename to modules.go index 1d9b4f20..ce6ec2b0 100644 --- a/commands/init.go +++ b/modules.go @@ -1,11 +1,40 @@ -package commands +package badaas import ( "strings" + "go.uber.org/fx" + "go.uber.org/zap" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/router" + "github.com/ditrit/badaas/router/middlewares" + "github.com/ditrit/badaas/services" "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" +) + +var InfoModule = fx.Module( + "info", + // controller + fx.Provide(controllers.NewInfoController), + // routes + fx.Invoke(router.AddInfoRoutes), +) + +var AuthModule = fx.Module( + "auth", + // service + services.AuthServiceModule, + + // controller + fx.Provide(controllers.NewBasicAuthenticationController), + + // routes + fx.Provide(middlewares.NewAuthenticationMiddleware), + fx.Invoke(router.AddAuthRoutes), + + fx.Invoke(createSuperUser), ) // Create a super user @@ -23,5 +52,6 @@ func createSuperUser( } logger.Sugar().Infof("The superadmin user already exists in database") } + return nil } diff --git a/commands/init_test.go b/modules_test.go similarity index 84% rename from commands/init_test.go rename to modules_test.go index 1966506d..05ce5c53 100644 --- a/commands/init_test.go +++ b/modules_test.go @@ -1,21 +1,22 @@ -package commands +package badaas import ( "errors" "testing" - mocks "github.com/ditrit/badaas/mocks/configuration" - mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + + mocksConfiguration "github.com/ditrit/badaas/mocks/configuration" + mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" ) func TestCreateSuperUser(t *testing.T) { core, _ := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. @@ -32,7 +33,7 @@ func TestCreateSuperUser(t *testing.T) { func TestCreateSuperUser_UserExists(t *testing.T) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. @@ -51,7 +52,7 @@ func TestCreateSuperUser_UserExists(t *testing.T) { func TestCreateSuperUser_UserServiceError(t *testing.T) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go new file mode 100644 index 00000000..a55b9bfa --- /dev/null +++ b/orm/ModuleFx.go @@ -0,0 +1,97 @@ +package orm + +import ( + "fmt" + "log" + "reflect" + + "go.uber.org/fx" +) + +type GetModelsResult struct { + fx.Out + + Models []any `group:"modelsTables"` +} + +var AutoMigrate = fx.Module( + "AutoMigrate", + fx.Invoke( + fx.Annotate( + autoMigrate, + fx.ParamTags(`group:"modelsTables"`), + ), + ), +) + +func GetCRUDServiceModule[T any]() fx.Option { + entity := *new(T) + + moduleName := fmt.Sprintf( + "%TCRUDServiceModule", + entity, + ) + + kind := getModelKind(entity) + switch kind { + case KindUUIDModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, UUID]), + // service + fx.Provide(NewCRUDService[T, UUID]), + ) + case KindUIntModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, uint]), + // service + fx.Provide(NewCRUDService[T, uint]), + ) + default: + log.Printf("type %T is not a BaDaaS model\n", entity) + return fx.Invoke(failNotBaDaaSModel()) + } +} + +func failNotBaDaaSModel() error { + return fmt.Errorf("type is not a BaDaaS model") +} + +type modelKind uint + +const ( + KindUUIDModel modelKind = iota + KindUIntModel + KindNotModel +) + +func getModelKind(entity any) modelKind { + entityType := getEntityType(entity) + + _, isUUIDModel := entityType.FieldByName("UUIDModel") + if isUUIDModel { + return KindUUIDModel + } + + _, isUIntModel := entityType.FieldByName("UIntModel") + if isUIntModel { + return KindUIntModel + } + + return KindNotModel +} + +// Get the reflect.Type of any entity or pointer to entity +func getEntityType(entity any) reflect.Type { + entityType := reflect.TypeOf(entity) + + // entityType will be a pointer if the relation can be nullable + if entityType.Kind() == reflect.Pointer { + entityType = entityType.Elem() + } + + return entityType +} diff --git a/orm/README.md b/orm/README.md new file mode 100644 index 00000000..240a0cff --- /dev/null +++ b/orm/README.md @@ -0,0 +1,47 @@ +# BaDaaS ORM: Backend and Distribution ORM (Object Relational Mapping) + +BaDaaS ORM is the BaDaaS component that allows for easy persistence and querying of objects. It is built on top of gorm and adds for each entity a service and a repository that allows complex queries without any extra effort. + +BaDaaS ORM can be used both within a BaDaaS application and as a stand-alone application. + +- [Installation](#installation) +- [Provided functionalities](#provided-functionalities) + - [Base models](#base-models) + - [CRUDServiceModule](#crudservicemodule) + +## Installation + +Once you have started your project with `go init`, you must add the dependency to BaDaaS: + +```bash +go get -u github.com/ditrit/badaas +``` + +## Provided functionalities + +### Base models + +badaas-orm gives you two types of base models for your classes: `orm.UUIDModel` and `orm.UIntModel`. + +To use them, simply embed the desired model in any of your classes: + +```go +type MyClass struct { + orm.UUIDModel + + // your code here +} +``` + +Once done your class will be considered a **BaDaaS Model**. + +The difference between them is the type they will use as primary key: a random uuid and an auto incremental uint respectively. Both provide date created, edited and deleted (). + +### CRUDServiceModule + +`CRUDServiceModule` provides you a CRUDService and a CRUDRepository for your badaas Model. After calling it as, for example, `orm.GetCRUDServiceModule[models.Company](),` the following can be used by dependency injection: + +- `crudCompanyService orm.CRUDService[models.Company, orm.UUID]` +- `crudCompanyRepository orm.CRUDRepository[models.Company, orm.UUID]` + +These classes will allow you to perform queries using the compilable query system generated with badaas-cli. For details on how to do this visit [badaas-cli docs](github.com/ditrit/badaas-cli/README.md). diff --git a/orm/baseModels.go b/orm/baseModels.go new file mode 100644 index 00000000..3a0b4816 --- /dev/null +++ b/orm/baseModels.go @@ -0,0 +1,34 @@ +package orm + +import ( + "time" + + "github.com/google/uuid" + + "gorm.io/gorm" +) + +// supported types for model identifier +type BadaasID interface { + uint | UUID +} + +// Base Model for gorm +// +// Every model intended to be saved in the database must embed this UUIDModel or UIntModel +// reference: https://gorm.io/docs/models.html#gorm-Model +type UUIDModel struct { + ID UUID `gorm:"primarykey;not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +func (model *UUIDModel) BeforeCreate(tx *gorm.DB) (err error) { + if model.ID == UUID(uuid.Nil) { + model.ID = UUID(uuid.New()) + } + return nil +} + +type UIntModel gorm.Model diff --git a/orm/condition.go b/orm/condition.go new file mode 100644 index 00000000..bb67a773 --- /dev/null +++ b/orm/condition.go @@ -0,0 +1,370 @@ +package orm + +import ( + "errors" + "fmt" + "strings" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" +) + +const DeletedAtField = "DeletedAt" + +var ErrEmptyConditions = errors.New("condition must have at least one inner condition") + +type Condition[T any] interface { + // Applies the condition to the "query" + // using the "tableName" as name for the table holding + // the data for object of type T + ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T], + // since if no method receives by parameter a type T, + // any other Condition[T2] would also be considered a Condition[T]. + interfaceVerificationMethod(T) +} + +// Conditions that can be used in a where clause +// (or in a on of a join) +type WhereCondition[T any] interface { + Condition[T] + + // Get the sql string and values to use in the query + GetSQL(query *gorm.DB, tableName string) (string, []any, error) + + // Returns true if the DeletedAt column if affected by the condition + // If no condition affects the DeletedAt, the verification that it's null will be added automatically + affectsDeletedAt() bool +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +type ContainerCondition[T any] struct { + ConnectionCondition WhereCondition[T] + Prefix string +} + +//nolint:unused // see inside +func (condition ContainerCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ContainerCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition ContainerCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { + sqlString, values, err := condition.ConnectionCondition.GetSQL(query, tableName) + if err != nil { + return "", nil, err + } + + sqlString = condition.Prefix + " (" + sqlString + ")" + + return sqlString, values, nil +} + +//nolint:unused // is used +func (condition ContainerCondition[T]) affectsDeletedAt() bool { + return condition.ConnectionCondition.affectsDeletedAt() +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +func NewContainerCondition[T any](prefix string, conditions ...WhereCondition[T]) WhereCondition[T] { + if len(conditions) == 0 { + return NewInvalidCondition[T](ErrEmptyConditions) + } + + return ContainerCondition[T]{ + Prefix: prefix, + ConnectionCondition: And(conditions...), + } +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +type ConnectionCondition[T any] struct { + Connector string + Conditions []WhereCondition[T] +} + +//nolint:unused // see inside +func (condition ConnectionCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ConnectionCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition ConnectionCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { + sqlStrings := []string{} + values := []any{} + + for _, internalCondition := range condition.Conditions { + internalSQLString, internalValues, err := internalCondition.GetSQL(query, tableName) + if err != nil { + return "", nil, err + } + + sqlStrings = append(sqlStrings, internalSQLString) + + values = append(values, internalValues...) + } + + return strings.Join(sqlStrings, " "+condition.Connector+" "), values, nil +} + +//nolint:unused // is used +func (condition ConnectionCondition[T]) affectsDeletedAt() bool { + return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { + return internalCondition.affectsDeletedAt() + }) +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +func NewConnectionCondition[T any](connector string, conditions ...WhereCondition[T]) WhereCondition[T] { + return ConnectionCondition[T]{ + Connector: connector, + Conditions: conditions, + } +} + +// Condition that verifies the value of a field, +// using the Operator +type FieldCondition[TObject any, TAtribute any] struct { + Field string + Column string + ColumnPrefix string + Operator Operator[TAtribute] +} + +//nolint:unused // see inside +func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod(_ TObject) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns a gorm Where condition that can be used +// to filter that the Field as a value of Value +func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[TObject](condition, query, tableName) +} + +func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, tableName string) (*gorm.DB, error) { + sql, values, err := condition.GetSQL(query, tableName) + if err != nil { + return nil, err + } + + if condition.affectsDeletedAt() { + query = query.Unscoped() + } + + return query.Where( + sql, + values..., + ), nil +} + +//nolint:unused // is used +func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { + return condition.Field == DeletedAtField +} + +func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { + columnName := condition.Column + if columnName == "" { + columnName = query.NamingStrategy.ColumnName(tableName, condition.Field) + } + + // add column prefix and table name once we know the column name + columnName = tableName + "." + condition.ColumnPrefix + columnName + + return condition.Operator.ToSQL(columnName) +} + +// Condition that joins with other table +type JoinCondition[T1 any, T2 any] struct { + T1Field string + T2Field string + Conditions []Condition[T2] +} + +func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Applies a join between the tables of T1 and T2 +// previousTableName is the name of the table of T1 +// It also applies the nested conditions +func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName string) (*gorm.DB, error) { + // get the name of the table for T2 + toBeJoinedTableName, err := getTableName(query, *new(T2)) + if err != nil { + return nil, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + nextTableName := toBeJoinedTableName + "_" + previousTableName + + // get the sql to do the join with T2 + joinQuery := condition.getSQLJoin(query, toBeJoinedTableName, nextTableName, previousTableName) + + whereConditions, joinConditions := divideConditionsByType(condition.Conditions) + + // apply WhereConditions to join in "on" clause + connectionCondition := And(whereConditions...) + + onQuery, onValues, err := connectionCondition.GetSQL(query, nextTableName) + if err != nil { + return nil, err + } + + if onQuery != "" { + joinQuery += " AND " + onQuery + } + + if !connectionCondition.affectsDeletedAt() { + joinQuery += fmt.Sprintf( + " AND %s.deleted_at IS NULL", + nextTableName, + ) + } + + // add the join to the query + query = query.Joins(joinQuery, onValues...) + + // apply nested joins + for _, joinCondition := range joinConditions { + query, err = joinCondition.ApplyTo(query, nextTableName) + if err != nil { + return nil, err + } + } + + return query, nil +} + +// Returns the SQL string to do a join between T1 and T2 +// taking into account that the ID attribute necessary to do it +// can be either in T1's or T2's table. +func (condition JoinCondition[T1, T2]) getSQLJoin(query *gorm.DB, toBeJoinedTableName, nextTableName, previousTableName string) string { + return fmt.Sprintf( + `JOIN %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s + `, + toBeJoinedTableName, + nextTableName, + query.NamingStrategy.ColumnName(nextTableName, condition.T2Field), + previousTableName, + query.NamingStrategy.ColumnName(previousTableName, condition.T1Field), + ) +} + +// Divides a list of conditions by its type: WhereConditions and JoinConditions +func divideConditionsByType[T any]( + conditions []Condition[T], +) (thisEntityConditions []WhereCondition[T], joinConditions []Condition[T]) { + for _, condition := range conditions { + typedCondition, ok := condition.(WhereCondition[T]) + if ok { + thisEntityConditions = append(thisEntityConditions, typedCondition) + } else { + joinConditions = append(joinConditions, condition) + } + } + + return +} + +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +type UnsafeCondition[T any] struct { + SQLCondition string + Values []any +} + +//nolint:unused // see inside +func (condition UnsafeCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition UnsafeCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition UnsafeCondition[T]) GetSQL(_ *gorm.DB, tableName string) (string, []any, error) { + return fmt.Sprintf( + condition.SQLCondition, + tableName, + ), condition.Values, nil +} + +//nolint:unused // is used +func (condition UnsafeCondition[T]) affectsDeletedAt() bool { + return false +} + +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +func NewUnsafeCondition[T any](condition string, values []any) UnsafeCondition[T] { + return UnsafeCondition[T]{ + SQLCondition: condition, + Values: values, + } +} + +// Condition used to returns an error when the query is executed +type InvalidCondition[T any] struct { + Err error +} + +//nolint:unused // see inside +func (condition InvalidCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition InvalidCondition[T]) ApplyTo(_ *gorm.DB, _ string) (*gorm.DB, error) { + return nil, condition.Err +} + +func (condition InvalidCondition[T]) GetSQL(_ *gorm.DB, _ string) (string, []any, error) { + return "", nil, condition.Err +} + +//nolint:unused // is used +func (condition InvalidCondition[T]) affectsDeletedAt() bool { + return false +} + +// Condition used to returns an error when the query is executed +func NewInvalidCondition[T any](err error) InvalidCondition[T] { + return InvalidCondition[T]{ + Err: err, + } +} + +// Logical Operators +// ref: https://www.postgresql.org/docs/current/functions-logical.html + +func And[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition("AND", conditions...) +} + +func Or[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition("OR", conditions...) +} + +func Not[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewContainerCondition("NOT", conditions...) +} diff --git a/orm/crudRepository.go b/orm/crudRepository.go new file mode 100644 index 00000000..b1d7883b --- /dev/null +++ b/orm/crudRepository.go @@ -0,0 +1,127 @@ +package orm + +import ( + "errors" + "sync" + + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +// Generic CRUD Repository +// T can be any model whose identifier attribute is of type ID +type CRUDRepository[T any, ID BadaasID] interface { + // Create model "model" inside transaction "tx" + Create(tx *gorm.DB, entity *T) error + + // ----- read ----- + // Get a model by its ID + GetByID(tx *gorm.DB, id ID) (*T, error) + + // Get only one model that match "conditions" inside transaction "tx" + // or returns error if 0 or more than 1 are found. + QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" inside transaction "tx" + Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) + + // Save model "model" inside transaction "tx" + Save(tx *gorm.DB, entity *T) error + + // Delete model "model" inside transaction "tx" + Delete(tx *gorm.DB, entity *T) error +} + +var ( + ErrMoreThanOneObjectFound = errors.New("found more that one object that meet the requested conditions") + ErrObjectNotFound = errors.New("no object exists that meets the requested conditions") +) + +// Implementation of the Generic CRUD Repository +type CRUDRepositoryImpl[T any, ID BadaasID] struct { + CRUDRepository[T, ID] +} + +// Constructor of the Generic CRUD Repository +func NewCRUDRepository[T any, ID BadaasID]() CRUDRepository[T, ID] { + return &CRUDRepositoryImpl[T, ID]{} +} + +// Create object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Create(tx *gorm.DB, entity *T) error { + return tx.Create(entity).Error +} + +// Delete object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Delete(tx *gorm.DB, entity *T) error { + return tx.Delete(entity).Error +} + +// Save object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Save(tx *gorm.DB, entity *T) error { + return tx.Save(entity).Error +} + +// Get a model by its ID +func (repository *CRUDRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + var model T + + err := tx.First(&model, "id = ?", id).Error + if err != nil { + return nil, err + } + + return &model, nil +} + +// Get only one model that match "conditions" inside transaction "tx" +// or returns error if 0 or more than 1 are found. +func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) { + entities, err := repository.Query(tx, conditions...) + if err != nil { + return nil, err + } + + switch { + case len(entities) == 1: + return entities[0], nil + case len(entities) == 0: + return nil, ErrObjectNotFound + default: + return nil, ErrMoreThanOneObjectFound + } +} + +// Get the list of models that match "conditions" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { + initialTableName, err := getTableName(tx, *new(T)) + if err != nil { + return nil, err + } + + query := tx + for _, condition := range conditions { + query, err = condition.ApplyTo(query, initialTableName) + if err != nil { + return nil, err + } + } + + // execute query + var entities []*T + err = query.Find(&entities).Error + + return entities, err +} + +// Get the name of the table in "db" in which the data for "entity" is saved +// returns error is table name can not be found by gorm, +// probably because the type of "entity" is not registered using AddModel +func getTableName(db *gorm.DB, entity any) (string, error) { + schemaName, err := schema.Parse(entity, &sync.Map{}, db.NamingStrategy) + if err != nil { + return "", err + } + + return schemaName.Table, nil +} diff --git a/orm/crudService.go b/orm/crudService.go new file mode 100644 index 00000000..eeb409bc --- /dev/null +++ b/orm/crudService.go @@ -0,0 +1,54 @@ +package orm + +import ( + "gorm.io/gorm" +) + +// T can be any model whose identifier attribute is of type ID +type CRUDService[T any, ID BadaasID] interface { + // Get the model of type T that has the "id" + GetByID(id ID) (*T, error) + + // Get only one model that match "conditions" + // or return error if 0 or more than 1 are found. + QueryOne(conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" + Query(conditions ...Condition[T]) ([]*T, error) +} + +// check interface compliance +var _ CRUDService[UUIDModel, UUID] = (*crudServiceImpl[UUIDModel, UUID])(nil) + +// Implementation of the CRUD Service +type crudServiceImpl[T any, ID BadaasID] struct { + CRUDService[T, ID] + db *gorm.DB + repository CRUDRepository[T, ID] +} + +func NewCRUDService[T any, ID BadaasID]( + db *gorm.DB, + repository CRUDRepository[T, ID], +) CRUDService[T, ID] { + return &crudServiceImpl[T, ID]{ + db: db, + repository: repository, + } +} + +// Get the model of type T that has the "id" +func (service *crudServiceImpl[T, ID]) GetByID(id ID) (*T, error) { + return service.repository.GetByID(service.db, id) +} + +// Get only one model that match "conditions" +// or return error if 0 or more than 1 are found. +func (service *crudServiceImpl[T, ID]) QueryOne(conditions ...Condition[T]) (*T, error) { + return service.repository.QueryOne(service.db, conditions...) +} + +// Get the list of models that match "conditions" +func (service *crudServiceImpl[T, ID]) Query(conditions ...Condition[T]) ([]*T, error) { + return service.repository.Query(service.db, conditions...) +} diff --git a/orm/db.go b/orm/db.go new file mode 100644 index 00000000..9b05a115 --- /dev/null +++ b/orm/db.go @@ -0,0 +1,54 @@ +package orm + +import ( + "fmt" + "time" + + "go.uber.org/zap" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" +) + +func CreateDialector(host, username, password, sslmode, dbname string, port int) gorm.Dialector { + return postgres.Open(CreateDSN( + host, username, password, sslmode, dbname, port, + )) +} + +func CreateDSN(host, username, password, sslmode, dbname string, port int) string { + return fmt.Sprintf( + "user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", + username, password, host, port, sslmode, dbname, + ) +} + +func ConnectToDialector( + logger *zap.Logger, + dialector gorm.Dialector, + retryAmount uint, + retryTime time.Duration, +) (*gorm.DB, error) { + var err error + var database *gorm.DB + for numberRetry := uint(0); numberRetry < retryAmount; numberRetry++ { + database, err = gorm.Open(dialector, &gorm.Config{ + Logger: gormzap.New(logger), + }) + + if err == nil { + logger.Sugar().Debugf("Database connection is active") + return database, nil + } + + logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) + logger.Sugar().Debugf( + "Retrying database connection %d/%d in %s", + numberRetry+1, retryAmount, retryTime.String(), + ) + time.Sleep(retryTime) + } + + return nil, err +} diff --git a/orm/db_test.go b/orm/db_test.go new file mode 100644 index 00000000..58b750ca --- /dev/null +++ b/orm/db_test.go @@ -0,0 +1,22 @@ +package orm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateDSN(t *testing.T) { + assert.Equal( + t, + "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", + CreateDSN( + "192.168.2.5", + "username", + "password", + "disable", + "badaas_db", + 1225, + ), + ) +} diff --git a/orm/operator.go b/orm/operator.go new file mode 100644 index 00000000..b2b83789 --- /dev/null +++ b/orm/operator.go @@ -0,0 +1,84 @@ +package orm + +import "fmt" + +type Operator[T any] interface { + // Transform the Operator to a SQL string and a list of values to use in the query + // columnName is used by the operator to determine which is the objective column. + ToSQL(columnName string) (string, []any, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T], + // since if no method receives by parameter a type T, + // any other Operator[T2] would also be considered a Operator[T]. + InterfaceVerificationMethod(T) +} + +// Operator that compares the value of the column against a fixed value +// If Operations has multiple entries, operations will be nested +// Example (single): value = v1 +// Example (multi): value LIKE v1 ESCAPE v2 +type ValueOperator[T any] struct { + Operations []operation +} + +type operation struct { + SQLOperator string + Value any +} + +func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (operator ValueOperator[T]) ToSQL(columnName string) (string, []any, error) { + operatorString := columnName + values := []any{} + + for _, operation := range operator.Operations { + operatorString += " " + operation.SQLOperator + " ?" + values = append(values, operation.Value) + } + + return operatorString, values, nil +} + +func NewValueOperator[T any](sqlOperator string, value any) ValueOperator[T] { + operator := ValueOperator[T]{} + + return operator.AddOperation(sqlOperator, value) +} + +func (operator *ValueOperator[T]) AddOperation(sqlOperator string, value any) ValueOperator[T] { + operator.Operations = append( + operator.Operations, + operation{ + Value: value, + SQLOperator: sqlOperator, + }, + ) + + return *operator +} + +// Operator that verifies a predicate +// Example: value IS TRUE +type PredicateOperator[T any] struct { + SQLOperator string +} + +func (operator PredicateOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (operator PredicateOperator[T]) ToSQL(columnName string) (string, []any, error) { + return fmt.Sprintf("%s %s", columnName, operator.SQLOperator), []any{}, nil +} + +func NewPredicateOperator[T any](sqlOperator string) PredicateOperator[T] { + return PredicateOperator[T]{ + SQLOperator: sqlOperator, + } +} diff --git a/orm/operators.go b/orm/operators.go new file mode 100644 index 00000000..a8093515 --- /dev/null +++ b/orm/operators.go @@ -0,0 +1,131 @@ +package orm + +// Comparison Operators +// ref: https://www.postgresql.org/docs/current/functions-comparison.html + +// EqualTo +// IsNotDistinct must be used in cases where value can be NULL +func Eq[T any](value T) Operator[T] { + return NewValueOperator[T]("=", value) +} + +// NotEqualTo +// IsDistinct must be used in cases where value can be NULL +func NotEq[T any](value T) Operator[T] { + return NewValueOperator[T]("<>", value) +} + +// LessThan +func Lt[T any](value T) Operator[T] { + return NewValueOperator[T]("<", value) +} + +// LessThanOrEqualTo +func LtOrEq[T any](value T) Operator[T] { + return NewValueOperator[T]("<=", value) +} + +// GreaterThan +func Gt[T any](value T) Operator[T] { + return NewValueOperator[T](">", value) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](value T) Operator[T] { + return NewValueOperator[T](">=", value) +} + +// Comparison Predicates +// refs: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to v1 < value < v2 +func Between[T any](v1 T, v2 T) Operator[T] { + return newBetweenOperator("BETWEEN", v1, v2) +} + +// Equivalent to NOT (v1 < value < v2) +func NotBetween[T any](v1 T, v2 T) Operator[T] { + return newBetweenOperator("NOT BETWEEN", v1, v2) +} + +func newBetweenOperator[T any](sqlOperator string, v1 T, v2 T) Operator[T] { + operator := NewValueOperator[T](sqlOperator, v1) + return operator.AddOperation("AND", v2) +} + +func IsNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NULL") +} + +func IsNotNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT NULL") +} + +// Boolean Comparison Predicates + +func IsTrue() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS TRUE") +} + +func IsNotTrue() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT TRUE") +} + +func IsFalse() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS FALSE") +} + +func IsNotFalse() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT FALSE") +} + +func IsUnknown() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS UNKNOWN") +} + +func IsNotUnknown() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT UNKNOWN") +} + +func IsDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T]("IS DISTINCT FROM", value) +} + +func IsNotDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T]("IS NOT DISTINCT FROM", value) +} + +// Row and Array Comparisons + +func ArrayIn[T any](values ...T) ValueOperator[T] { + return NewValueOperator[T]("IN", values) +} + +func ArrayNotIn[T any](values ...T) ValueOperator[T] { + return NewValueOperator[T]("NOT IN", values) +} + +// Pattern Matching + +type LikeOperator struct { + ValueOperator[string] +} + +func NewLikeOperator(sqlOperator string, pattern string) LikeOperator { + return LikeOperator{ + ValueOperator: NewValueOperator[string](sqlOperator, pattern), + } +} + +func (operator LikeOperator) Escape(escape rune) ValueOperator[string] { + return operator.AddOperation("ESCAPE", string(escape)) +} + +// Patterns: +// - An underscore (_) in pattern stands for (matches) any single character. +// - A percent sign (%) matches any sequence of zero or more characters. +// +// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE +func Like(pattern string) LikeOperator { + return NewLikeOperator("LIKE", pattern) +} diff --git a/orm/orm.go b/orm/orm.go new file mode 100644 index 00000000..0f525279 --- /dev/null +++ b/orm/orm.go @@ -0,0 +1,20 @@ +package orm + +import ( + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" +) + +func GetCRUD[T any, ID BadaasID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { + repository := NewCRUDRepository[T, ID]() + return NewCRUDService(db, repository), repository +} + +func autoMigrate(modelsLists [][]any, db *gorm.DB) error { + if len(modelsLists) > 0 { + allModels := pie.Flat(modelsLists) + return db.AutoMigrate(allModels...) + } + + return nil +} diff --git a/orm/uuid.go b/orm/uuid.go new file mode 100644 index 00000000..6b6ca817 --- /dev/null +++ b/orm/uuid.go @@ -0,0 +1,88 @@ +package orm + +import ( + "context" + "database/sql/driver" + + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" +) + +type UUID uuid.UUID + +var NilUUID = UUID(uuid.Nil) + +func (id UUID) GormDBDataType(_ *gorm.DB, _ *schema.Field) string { + return "uuid" +} + +func (id UUID) String() string { + return uuid.UUID(id).String() +} + +func (id UUID) URN() string { + return uuid.UUID(id).URN() +} + +func (id UUID) Variant() uuid.Variant { + return uuid.UUID(id).Variant() +} + +func (id UUID) Version() uuid.Version { + return uuid.UUID(id).Version() +} + +func (id UUID) MarshalText() ([]byte, error) { + return uuid.UUID(id).MarshalText() +} + +func (id *UUID) UnmarshalText(data []byte) error { + return (*uuid.UUID)(id).UnmarshalText(data) +} + +func (id UUID) MarshalBinary() ([]byte, error) { + return uuid.UUID(id).MarshalBinary() +} + +func (id *UUID) UnmarshalBinary(data []byte) error { + return (*uuid.UUID)(id).UnmarshalBinary(data) +} + +func (id *UUID) Scan(src interface{}) error { + return (*uuid.UUID)(id).Scan(src) +} + +func (id UUID) GormValue(_ context.Context, _ *gorm.DB) clause.Expr { + if len(id) == 0 { + return gorm.Expr("NULL") + } + + return gorm.Expr("?", id.String()) +} + +func (id UUID) Value() (driver.Value, error) { + return uuid.UUID(id).Value() +} + +func (id UUID) Time() uuid.Time { + return uuid.UUID(id).Time() +} + +func (id UUID) ClockSequence() int { + return uuid.UUID(id).ClockSequence() +} + +func NewUUID() UUID { + return UUID(uuid.New()) +} + +func ParseUUID(s string) (UUID, error) { + uid, err := uuid.Parse(s) + if err != nil { + return UUID(uuid.Nil), err + } + + return UUID(uid), nil +} diff --git a/orm/uuid_test.go b/orm/uuid_test.go new file mode 100644 index 00000000..1e6828ec --- /dev/null +++ b/orm/uuid_test.go @@ -0,0 +1,23 @@ +package orm_test + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" +) + +func TestParseCorrectUUID(t *testing.T) { + uuidString := uuid.New().String() + uuid, err := orm.ParseUUID(uuidString) + assert.Nil(t, err) + assert.Equal(t, uuidString, uuid.String()) +} + +func TestParseIncorrectUUID(t *testing.T) { + uid, err := orm.ParseUUID("not uuid") + assert.Error(t, err) + assert.Equal(t, orm.NilUUID, uid) +} diff --git a/persistence/ModuleFx.go b/persistence/ModuleFx.go index d99d1c02..4277f90f 100644 --- a/persistence/ModuleFx.go +++ b/persistence/ModuleFx.go @@ -1,11 +1,10 @@ package persistence import ( - "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" "go.uber.org/fx" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/gormdatabase" ) // PersistanceModule for fx @@ -13,14 +12,11 @@ import ( // Provides: // // - The database connection -// -// - The repositories +// - badaas-orm auto-migration var PersistanceModule = fx.Module( "persistence", // Database connection - fx.Provide(gormdatabase.CreateDatabaseConnectionFromConfiguration), - - //repositories - fx.Provide(repository.NewCRUDRepository[models.Session, uuid.UUID]), - fx.Provide(repository.NewCRUDRepository[models.User, uuid.UUID]), + fx.Provide(gormdatabase.SetupDatabaseConnection), + // auto-migrate + orm.AutoMigrate, ) diff --git a/persistence/gormdatabase/db.go b/persistence/gormdatabase/db.go index ddd5a22b..26776e52 100644 --- a/persistence/gormdatabase/db.go +++ b/persistence/gormdatabase/db.go @@ -1,20 +1,16 @@ package gormdatabase import ( - "fmt" - "time" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" - "github.com/ditrit/badaas/persistence/models" "go.uber.org/zap" - "gorm.io/driver/postgres" "gorm.io/gorm" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/orm" ) // Create the dsn string from the configuration -func createDsnFromConf(databaseConfiguration configuration.DatabaseConfiguration) string { - dsn := createDsn( +func createDialectorFromConf(databaseConfiguration configuration.DatabaseConfiguration) gorm.Dialector { + return orm.CreateDialector( databaseConfiguration.GetHost(), databaseConfiguration.GetUsername(), databaseConfiguration.GetPassword(), @@ -22,77 +18,19 @@ func createDsnFromConf(databaseConfiguration configuration.DatabaseConfiguration databaseConfiguration.GetDBName(), databaseConfiguration.GetPort(), ) - return dsn } -// Create the dsn strings with the provided args -func createDsn(host, username, password, sslmode, dbname string, port int) string { - return fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", - username, password, host, port, sslmode, dbname, +// Creates the database object with using the database configuration and exec the setup +func SetupDatabaseConnection( + logger *zap.Logger, + databaseConfiguration configuration.DatabaseConfiguration, +) (*gorm.DB, error) { + dialector := createDialectorFromConf(databaseConfiguration) + + return orm.ConnectToDialector( + logger, + dialector, + databaseConfiguration.GetRetry(), + databaseConfiguration.GetRetryTime(), ) } - -// Initialize the database with using the database configuration -func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { - dsn := createDsnFromConf(databaseConfiguration) - var err error - var database *gorm.DB - for numberRetry := uint(0); numberRetry < databaseConfiguration.GetRetry(); numberRetry++ { - database, err = initializeDBFromDsn(dsn, logger) - if err == nil { - logger.Sugar().Debugf("Database connection is active") - err = AutoMigrate(logger, database) - if err != nil { - logger.Error("migration failed") - return nil, err - } - logger.Info("AutoMigration was executed successfully") - return database, err - } - logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) - logger.Sugar().Debugf("Retrying database connection %d/%d in %s", - numberRetry+1, databaseConfiguration.GetRetry(), databaseConfiguration.GetRetryTime().String()) - time.Sleep(databaseConfiguration.GetRetryTime()) - } - return nil, err -} - -// Initialize the database with the dsn string -func initializeDBFromDsn(dsn string, logger *zap.Logger) (*gorm.DB, error) { - database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: gormzap.New(logger), - }) - - if err != nil { - return nil, err - } - - rawDatabase, err := database.DB() - if err != nil { - return nil, err - } - // ping the underlying database - err = rawDatabase.Ping() - if err != nil { - return nil, err - } - return database, nil -} - -// Migrate the database using gorm [https://gorm.io/docs/migration.html#Auto-Migration] -func autoMigrate(database *gorm.DB, listOfDatabaseTables []any) error { - err := database.AutoMigrate(listOfDatabaseTables...) - if err != nil { - return err - } - return nil -} - -// Run the automigration -func AutoMigrate(logger *zap.Logger, database *gorm.DB) error { - err := autoMigrate(database, models.ListOfTables) - if err != nil { - return err - } - return nil -} diff --git a/persistence/gormdatabase/db_test.go b/persistence/gormdatabase/db_test.go deleted file mode 100644 index 05eef996..00000000 --- a/persistence/gormdatabase/db_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package gormdatabase - -import ( - "testing" - - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" -) - -func TestCreateDsnFromconf(t *testing.T) { - conf := configurationmocks.NewDatabaseConfiguration(t) - conf.On("GetPort").Return(1225) - conf.On("GetHost").Return("192.168.2.5") - conf.On("GetDBName").Return("badaas_db") - conf.On("GetUsername").Return("username") - conf.On("GetPassword").Return("password") - conf.On("GetSSLMode").Return("disable") - assert.Equal(t, "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsnFromConf(conf)) -} - -func TestCreateDsn(t *testing.T) { - assert.Equal(t, - "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsn( - "192.168.2.5", - "username", - "password", - "disable", - "badaas_db", - 1225, - ), - "no dsn should be empty", - ) -} diff --git a/persistence/models/BaseModel.go b/persistence/models/BaseModel.go deleted file mode 100644 index acf3c2e0..00000000 --- a/persistence/models/BaseModel.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -// Base Model for gorm -// -// Every model intended to be saved in the database must embed this BaseModel -// reference: https://gorm.io/docs/models.html#gorm-Model -type BaseModel struct { - ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} diff --git a/persistence/models/ProductInfo.go b/persistence/models/ProductInfo.go deleted file mode 100644 index 42040ce5..00000000 --- a/persistence/models/ProductInfo.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -// Describe the current BADAAS instance -type BadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/persistence/models/Session.go b/persistence/models/Session.go index 89bdb1b7..0b92fee9 100644 --- a/persistence/models/Session.go +++ b/persistence/models/Session.go @@ -3,16 +3,24 @@ package models import ( "time" - "github.com/google/uuid" + "github.com/ditrit/badaas/orm" ) // Represent a user session type Session struct { - BaseModel - UserID uuid.UUID `gorm:"not null"` + orm.UUIDModel + UserID orm.UUID `gorm:"not null"` ExpiresAt time.Time `gorm:"not null"` } +// Create a new session +func NewSession(userID orm.UUID, sessionDuration time.Duration) *Session { + return &Session{ + UserID: userID, + ExpiresAt: time.Now().Add(sessionDuration), + } +} + // Return true is expired func (session *Session) IsExpired() bool { return time.Now().After(session.ExpiresAt) @@ -22,10 +30,3 @@ func (session *Session) IsExpired() bool { func (session *Session) CanBeRolled(rollInterval time.Duration) bool { return time.Now().After(session.ExpiresAt.Add(-rollInterval)) } - -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (Session) TableName() string { - return "sessions" -} diff --git a/persistence/models/Session_test.go b/persistence/models/Session_test.go index a1fdd240..7d0746e9 100644 --- a/persistence/models/Session_test.go +++ b/persistence/models/Session_test.go @@ -4,10 +4,18 @@ import ( "testing" "time" - "github.com/ditrit/badaas/persistence/models" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" ) +func TestNewSession(t *testing.T) { + sessionInstance := models.NewSession(orm.NilUUID, time.Second) + assert.NotNil(t, sessionInstance) + assert.Equal(t, orm.NilUUID, sessionInstance.UserID) +} + func TestExpired(t *testing.T) { sessionInstance := &models.Session{ ExpiresAt: time.Now().Add(time.Second), @@ -26,7 +34,3 @@ func TestCanBeRolled(t *testing.T) { time.Sleep(400 * time.Millisecond) assert.True(t, sessionInstance.CanBeRolled(sessionDuration)) } - -func TestTableName(t *testing.T) { - assert.Equal(t, "sessions", models.Session{}.TableName()) -} diff --git a/persistence/models/Tabler.go b/persistence/models/Tabler.go deleted file mode 100644 index eac89ab8..00000000 --- a/persistence/models/Tabler.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -var ListOfTables = []any{ - User{}, - Session{}, -} - -// The interface "type" need to implement to be considered models -type Tabler interface { - // pluralized name - TableName() string -} diff --git a/persistence/models/User.go b/persistence/models/User.go index d65aa425..0a38a6ed 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -1,8 +1,10 @@ package models +import "github.com/ditrit/badaas/orm" + // Represents a user type User struct { - BaseModel + orm.UUIDModel Username string `gorm:"not null"` Email string `gorm:"unique;not null"` @@ -10,9 +12,9 @@ type User struct { Password []byte `gorm:"not null"` } -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (User) TableName() string { - return "users" +func UserEmailCondition(operator orm.Operator[string]) orm.WhereCondition[User] { + return orm.FieldCondition[User, string]{ + Operator: operator, + Field: "Email", + } } diff --git a/persistence/models/dto/ProductInfo.go b/persistence/models/dto/ProductInfo.go deleted file mode 100644 index 988650ea..00000000 --- a/persistence/models/dto/ProductInfo.go +++ /dev/null @@ -1,9 +0,0 @@ -package dto - -// Data Transfert Object Package - -// Describe the Server Info payload -type DTOBadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/persistence/pagination/Page.go b/persistence/pagination/Page.go deleted file mode 100644 index 2d76a050..00000000 --- a/persistence/pagination/Page.go +++ /dev/null @@ -1,40 +0,0 @@ -package pagination - -import "github.com/ditrit/badaas/persistence/models" - -// A page hold ressources and data regarding the pagination -type Page[T models.Tabler] struct { - Ressources []*T `json:"ressources"` - // max d'element par page - Limit uint `json:"limit"` - // page courante - Offset uint `json:"offset"` - // total d'element en base - Total uint `json:"total"` - // total de pages - TotalPages uint `json:"totalPages"` - HasNextPage bool `json:"hasNextpage"` - HasPreviousPage bool `json:"hasPreviousPage"` - IsFirstPage bool `json:"isFirstPage"` - IsLastPage bool `json:"isLastPage"` - HasContent bool `json:"hasContent"` -} - -// Create a new page -func NewPage[T models.Tabler](records []*T, offset, size, nbElemTotal uint) *Page[T] { - nbPage := nbElemTotal / size - p := Page[T]{ - Ressources: records, - Limit: size, - Offset: offset, - Total: nbElemTotal, - TotalPages: nbPage, - - HasNextPage: nbElemTotal > (offset+1)*size, - HasPreviousPage: offset >= 1, - IsFirstPage: offset == 0, - IsLastPage: offset == (nbPage - 1), - HasContent: len(records) != 0, - } - return &p -} diff --git a/persistence/pagination/Page_test.go b/persistence/pagination/Page_test.go deleted file mode 100644 index 999e5201..00000000 --- a/persistence/pagination/Page_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -type Whatever struct { - DumbData int -} - -func (Whatever) TableName() string { - return "whatevers" -} - -var ( - // test fixture - ressources = []*Whatever{ - {10}, - {11}, - {12}, - {13}, - {14}, - {15}, - {16}, - {17}, - {18}, - {19}, - } -) - -func TestNewPage(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.ElementsMatch(t, ressources, p.Ressources) - assert.Equal(t, uint(10), p.Limit) - assert.Equal(t, uint(1), p.Offset) - assert.Equal(t, uint(5), p.TotalPages) -} - -func TestPageHasNextPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasNextPage) -} - -func TestPageHasNextPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasNextPage) -} - -func TestPageIsLastPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsLastPage) -} - -func TestPageIsLastPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsLastPage) -} - -func TestPageHasPreviousPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasPreviousPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasPreviousPage) -} - -func TestPageIsFirstPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsFirstPage) -} - -func TestPageIsFirstPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 0: first page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsFirstPage) -} - -func TestPageHasContentFalse(t *testing.T) { - p := pagination.NewPage( - []*Whatever{}, // no content - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasContentTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasContent) -} diff --git a/persistence/pagination/Paginator.go b/persistence/pagination/Paginator.go deleted file mode 100644 index e2811a72..00000000 --- a/persistence/pagination/Paginator.go +++ /dev/null @@ -1,36 +0,0 @@ -package pagination - -// Handle pagination -type Paginator interface { - Offset() uint - Limit() uint -} - -type paginatorImpl struct { - offset uint - limit uint -} - -// Constructor of Paginator -func NewPaginator(page, limit uint) Paginator { - if page == 0 { - page = 1 - } - if limit == 0 { - limit = 1 - } - return &paginatorImpl{ - offset: page, - limit: limit, - } -} - -// Return the page number -func (p *paginatorImpl) Offset() uint { - return p.offset -} - -// Return the max number of records for one page -func (p *paginatorImpl) Limit() uint { - return p.limit -} diff --git a/persistence/pagination/Paginator_test.go b/persistence/pagination/Paginator_test.go deleted file mode 100644 index 8fc58329..00000000 --- a/persistence/pagination/Paginator_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -func TestPaginator(t *testing.T) { - paginator := pagination.NewPaginator(uint(0), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(0)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(1), paginator.Limit()) -} diff --git a/persistence/repository/CRUDRepository.go b/persistence/repository/CRUDRepository.go deleted file mode 100644 index e3c45662..00000000 --- a/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,20 +0,0 @@ -package repository - -import ( - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" -) - -// Generic CRUD Repository -type CRUDRepository[T models.Tabler, ID any] interface { - Create(*T) httperrors.HTTPError - Delete(*T) httperrors.HTTPError - Save(*T) httperrors.HTTPError - GetByID(ID) (*T, httperrors.HTTPError) - GetAll(SortOption) ([]*T, httperrors.HTTPError) - Count(squirrel.Sqlizer) (uint, httperrors.HTTPError) - Find(squirrel.Sqlizer, pagination.Paginator, SortOption) (*pagination.Page[T], httperrors.HTTPError) - Transaction(fn func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) -} diff --git a/persistence/repository/CRUDRepositoryImpl.go b/persistence/repository/CRUDRepositoryImpl.go deleted file mode 100644 index eca2365f..00000000 --- a/persistence/repository/CRUDRepositoryImpl.go +++ /dev/null @@ -1,240 +0,0 @@ -package repository - -import ( - "fmt" - "net/http" - - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" - "go.uber.org/zap" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -// Return a database error -func DatabaseError(message string, golangError error) httperrors.HTTPError { - return httperrors.NewInternalServerError( - "database error", - message, - golangError, - ) -} - -// Implementation of the Generic CRUD Repository -type CRUDRepositoryImpl[T models.Tabler, ID any] struct { - CRUDRepository[T, ID] - gormDatabase *gorm.DB - logger *zap.Logger - paginationConfiguration configuration.PaginationConfiguration -} - -// Contructor of the Generic CRUD Repository -func NewCRUDRepository[T models.Tabler, ID any]( - database *gorm.DB, - logger *zap.Logger, - paginationConfiguration configuration.PaginationConfiguration, -) CRUDRepository[T, ID] { - return &CRUDRepositoryImpl[T, ID]{ - gormDatabase: database, - logger: logger, - paginationConfiguration: paginationConfiguration, - } -} - -// Run the function passed as parameter, if it returns the error and rollback the transaction. -// If no error is returned, it commits the transaction and return the interface{} value. -func (repository *CRUDRepositoryImpl[T, ID]) Transaction(transactionFunction func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - } - }() - returnValue, err := transactionFunction(&CRUDRepositoryImpl[T, ID]{gormDatabase: transaction}) - if err != nil { - transaction.Rollback() - return nil, DatabaseError("transaction failed", err) - } - err = transaction.Commit().Error - if err != nil { - return nil, DatabaseError("transaction failed to commit", err) - } - return returnValue, nil -} - -// Create an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Create(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Create(entity).Error - if err != nil { - if gormdatabase.IsDuplicateKeyError(err) { - return httperrors.NewHTTPError( - http.StatusConflict, - fmt.Sprintf("%T already exist in database", entity), - "", - nil, false) - } - return DatabaseError( - fmt.Sprintf("could not create %v in %s", entity, (*entity).TableName()), - err, - ) - - } - return nil -} - -// Delete an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Delete(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Delete(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not delete %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Save an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Save(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Save(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not save user %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Get an entity of a Model By ID -func (repository *CRUDRepositoryImpl[T, ID]) GetByID(id ID) (*T, httperrors.HTTPError) { - var entity T - transaction := repository.gormDatabase.First(&entity, "id = ?", id) - if transaction.Error != nil { - return nil, DatabaseError( - fmt.Sprintf("could not get %s by id %v", entity.TableName(), id), - transaction.Error, - ) - } - return &entity, nil -} - -// Get all entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) GetAll(sortOption SortOption) ([]*T, httperrors.HTTPError) { - var entities []*T - transaction := repository.gormDatabase - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction.Find(&entities) - if transaction.Error != nil { - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get %s", emptyInstanceForError.TableName()), - transaction.Error, - ) - } - return entities, nil -} - -// Build a gorm order clause from a SortOption -func buildClauseFromSortOption(sortOption SortOption) clause.OrderByColumn { - return clause.OrderByColumn{Column: clause.Column{Name: sortOption.Column()}, Desc: sortOption.Desc()} -} - -// Count entities of a models -func (repository *CRUDRepositoryImpl[T, ID]) Count(filters squirrel.Sqlizer) (uint, httperrors.HTTPError) { - whereClause, values, httpError := repository.compileSQL(filters) - if httpError != nil { - return 0, httpError - } - return repository.count(whereClause, values) -} - -// Count the number of record that match the where clause with the provided values on the db -func (repository *CRUDRepositoryImpl[T, ID]) count(whereClause string, values []interface{}) (uint, httperrors.HTTPError) { - var entity *T - var count int64 - transaction := repository.gormDatabase.Model(entity).Where(whereClause, values).Count(&count) - if transaction.Error != nil { - var emptyInstanceForError T - return 0, DatabaseError( - fmt.Sprintf("could not count data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - return uint(count), nil -} - -// Find entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Find( - filters squirrel.Sqlizer, - page pagination.Paginator, - sortOption SortOption, -) (*pagination.Page[T], httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - - } - }() - var instances []*T - whereClause, values, httpError := repository.compileSQL(filters) - - if httpError != nil { - return nil, httpError - } - if page != nil { - transaction = transaction. - Offset( - int((page.Offset() - 1) * page.Limit()), - ). - Limit( - int(page.Limit()), - ) - } else { - page = pagination.NewPaginator(0, repository.paginationConfiguration.GetMaxElemPerPage()) - } - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction = transaction.Where(whereClause, values...).Find(&instances) - if transaction.Error != nil { - transaction.Rollback() - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - // Get Count - nbElem, httpError := repository.count(whereClause, values) - if httpError != nil { - transaction.Rollback() - return nil, httpError - } - err := transaction.Commit().Error - if err != nil { - return nil, DatabaseError( - "transaction failed to commit", err) - } - return pagination.NewPage(instances, page.Offset(), page.Limit(), nbElem), nil -} - -// compile the sql where clause -func (repository *CRUDRepositoryImpl[T, ID]) compileSQL(filters squirrel.Sqlizer) (string, []interface{}, httperrors.HTTPError) { - compiledSQLString, values, err := filters.ToSql() - if err != nil { - return "", []interface{}{}, httperrors.NewInternalServerError( - "sql error", - fmt.Sprintf("Failed to build the sql request (condition=%v)", filters), - err, - ) - } - return compiledSQLString, values, nil -} diff --git a/persistence/repository/CRUDRepositoryImpl_test.go b/persistence/repository/CRUDRepositoryImpl_test.go deleted file mode 100644 index 1dac5dbd..00000000 --- a/persistence/repository/CRUDRepositoryImpl_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package repository - -import ( - "testing" - - "github.com/Masterminds/squirrel" - mocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestDatabaseError(t *testing.T) { - err := DatabaseError("test err", assert.AnError) - require.NotNil(t, err) - assert.True(t, err.Log()) -} - -type dumbModel struct{} - -func (dumbModel) TableName() string { - return "dumb_models" -} - -func TestNewRepository(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := NewCRUDRepository[dumbModel, uint](nil, zap.L(), paginationConfiguration) - assert.NotNil(t, dumbModelRepository) -} - -func TestCompileSql_NoError(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.Eq{"name": "qsdqsd"}) - assert.Nil(t, err) -} - -func TestCompileSql_Err(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.GtOrEq{"name": nil}) - - assert.Error(t, err) -} diff --git a/persistence/repository/SortOption.go b/persistence/repository/SortOption.go deleted file mode 100644 index 0fda0c40..00000000 --- a/persistence/repository/SortOption.go +++ /dev/null @@ -1,27 +0,0 @@ -package repository - -type SortOption interface { - Column() string - Desc() bool -} - -// SortOption constructor -func NewSortOption(column string, desc bool) SortOption { - return &sortOption{column, desc} -} - -// Sorting option for the repository -type sortOption struct { - column string - desc bool -} - -// return the column name to sort on -func (sortOption *sortOption) Column() string { - return sortOption.column -} - -// return true for descending sort and false for ascending -func (sortOption *sortOption) Desc() bool { - return sortOption.desc -} diff --git a/persistence/repository/SortOption_test.go b/persistence/repository/SortOption_test.go deleted file mode 100644 index 89eba6c7..00000000 --- a/persistence/repository/SortOption_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package repository_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/repository" - "github.com/stretchr/testify/assert" -) - -func TestNewSortOption(t *testing.T) { - sortOption := repository.NewSortOption("a", true) - assert.Equal(t, "a", sortOption.Column()) - assert.True(t, sortOption.Desc()) -} diff --git a/resources/api.go b/resources/api.go deleted file mode 100644 index ddea375f..00000000 --- a/resources/api.go +++ /dev/null @@ -1,4 +0,0 @@ -package resources - -// Version of Badaas -const Version = "UNRELEASED" diff --git a/router/ModuleFx.go b/router/ModuleFx.go index a9f9d763..cda5eed7 100644 --- a/router/ModuleFx.go +++ b/router/ModuleFx.go @@ -1,19 +1,17 @@ package router import ( - "github.com/ditrit/badaas/router/middlewares" "go.uber.org/fx" + + "github.com/ditrit/badaas/router/middlewares" ) // RouterModule for fx var RouterModule = fx.Module( "router", + fx.Provide(NewRouter), // middlewares fx.Provide(middlewares.NewJSONController), fx.Provide(middlewares.NewMiddlewareLogger), - - fx.Provide(middlewares.NewAuthenticationMiddleware), - - // create router - fx.Provide(SetupRouter), + fx.Invoke(middlewares.AddLoggerMiddleware), ) diff --git a/router/middlewares/middlewareAuthentification.go b/router/middlewares/middlewareAuthentication.go similarity index 90% rename from router/middlewares/middlewareAuthentification.go rename to router/middlewares/middlewareAuthentication.go index aaf8d4b1..66f5a956 100644 --- a/router/middlewares/middlewareAuthentification.go +++ b/router/middlewares/middlewareAuthentication.go @@ -3,15 +3,14 @@ package middlewares import ( "net/http" + "go.uber.org/zap" + "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/services/sessionservice" - "github.com/google/uuid" - "go.uber.org/zap" ) -var ( - NotAuthenticated = httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") -) +var NotAuthenticated = httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") // The authentication middleware type AuthenticationMiddleware interface { @@ -43,7 +42,8 @@ func (authenticationMiddleware *authenticationMiddleware) Handle(next http.Handl NotAuthenticated.Write(response, authenticationMiddleware.logger) return } - extractedUUID, err := uuid.Parse(accessTokenCookie.Value) + + extractedUUID, err := orm.ParseUUID(accessTokenCookie.Value) if err != nil { NotAuthenticated.Write(response, authenticationMiddleware.logger) return diff --git a/router/middlewares/middlewareJson.go b/router/middlewares/middlewareJson.go index 1abe0343..c503f68e 100644 --- a/router/middlewares/middlewareJson.go +++ b/router/middlewares/middlewareJson.go @@ -4,8 +4,9 @@ import ( "encoding/json" "net/http" - "github.com/ditrit/badaas/httperrors" "go.uber.org/zap" + + "github.com/ditrit/badaas/httperrors" ) // transform a JSON handler into a standard [http.HandlerFunc] @@ -27,7 +28,8 @@ func NewJSONController(logger *zap.Logger) JSONController { return &jsonControllerImpl{logger} } -// Marshall the response from the JSONHandler and handle HTTPError if needed +// Transforms a JSONHandler into a standard [http.HandlerFunc] +// It marshalls the response from the JSONHandler and handles HTTPError if needed func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response http.ResponseWriter, request *http.Request) { return func(response http.ResponseWriter, request *http.Request) { object, herr := handler(response, request) @@ -48,6 +50,12 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht return } response.Header().Set("Content-Type", "application/json") - response.Write(payload) + _, err = response.Write(payload) + if err != nil { + controller.logger.Error( + "Error while writing http response", + zap.String("error", err.Error()), + ) + } } } diff --git a/router/middlewares/middlewareLogger.go b/router/middlewares/middlewareLogger.go index 00f436ad..2d792e4b 100644 --- a/router/middlewares/middlewareLogger.go +++ b/router/middlewares/middlewareLogger.go @@ -4,12 +4,18 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/configuration" + "github.com/gorilla/mux" "github.com/noirbizarre/gonja" "github.com/noirbizarre/gonja/exec" "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" ) +func AddLoggerMiddleware(router *mux.Router, middlewareLogger MiddlewareLogger) { + router.Use(middlewareLogger.Handle) +} + // Log the requests data type MiddlewareLogger interface { // [github.com/gorilla/mux] compatible middleware function diff --git a/router/router.go b/router/router.go index 8a3088cc..24039d72 100644 --- a/router/router.go +++ b/router/router.go @@ -1,42 +1,10 @@ package router import ( - "net/http" - - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/router/middlewares" "github.com/gorilla/mux" ) -// Default router of badaas, initialize all routes. -func SetupRouter( - //middlewares - jsonController middlewares.JSONController, - middlewareLogger middlewares.MiddlewareLogger, - authenticationMiddleware middlewares.AuthenticationMiddleware, - - // controllers - basicAuthentificationController controllers.BasicAuthentificationController, - informationController controllers.InformationController, -) http.Handler { - router := mux.NewRouter() - router.Use(middlewareLogger.Handle) - - router.HandleFunc( - "/info", - jsonController.Wrap(informationController.Info), - ).Methods("GET") - router.HandleFunc( - "/login", - jsonController.Wrap( - basicAuthentificationController.BasicLoginHandler, - ), - ).Methods("POST") - - protected := router.PathPrefix("").Subrouter() - protected.Use(authenticationMiddleware.Handle) - - protected.HandleFunc("/logout", jsonController.Wrap(basicAuthentificationController.Logout)).Methods("GET") - - return router +// Router to use in Badaas server +func NewRouter() *mux.Router { + return mux.NewRouter() } diff --git a/router/router_test.go b/router/router_test.go deleted file mode 100644 index da2f247a..00000000 --- a/router/router_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package router - -import ( - "net/http" - "testing" - - controllersMocks "github.com/ditrit/badaas/mocks/controllers" - middlewaresMocks "github.com/ditrit/badaas/mocks/router/middlewares" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestSetupRouter(t *testing.T) { - jsonController := middlewaresMocks.NewJSONController(t) - middlewareLogger := middlewaresMocks.NewMiddlewareLogger(t) - authenticationMiddleware := middlewaresMocks.NewAuthenticationMiddleware(t) - - basicController := controllersMocks.NewBasicAuthentificationController(t) - informationController := controllersMocks.NewInformationController(t) - jsonController.On("Wrap", mock.Anything).Return(func(response http.ResponseWriter, request *http.Request) {}) - router := SetupRouter(jsonController, middlewareLogger, authenticationMiddleware, basicController, informationController) - assert.NotNil(t, router) -} diff --git a/router/routes.go b/router/routes.go new file mode 100644 index 00000000..68d9fe38 --- /dev/null +++ b/router/routes.go @@ -0,0 +1,43 @@ +package router + +import ( + "github.com/gorilla/mux" + + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/router/middlewares" +) + +func AddInfoRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + infoController controllers.InformationController, +) { + router.HandleFunc( + "/info", + jsonController.Wrap(infoController.Info), + ).Methods("GET") +} + +// Adds to the "router" the routes for handling authentication: +// /login +// /logout +// And creates a very first user +func AddAuthRoutes( + router *mux.Router, + authenticationMiddleware middlewares.AuthenticationMiddleware, + basicAuthenticationController controllers.BasicAuthenticationController, + jsonController middlewares.JSONController, +) { + router.HandleFunc( + "/login", + jsonController.Wrap(basicAuthenticationController.BasicLoginHandler), + ).Methods("POST") + + protected := router.PathPrefix("").Subrouter() + protected.Use(authenticationMiddleware.Handle) + + protected.HandleFunc( + "/logout", + jsonController.Wrap(basicAuthenticationController.Logout), + ).Methods("GET") +} diff --git a/router/routes_test.go b/router/routes_test.go new file mode 100644 index 00000000..fb8b5161 --- /dev/null +++ b/router/routes_test.go @@ -0,0 +1,72 @@ +package router + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.uber.org/zap" + + "github.com/ditrit/badaas/controllers" + mockControllers "github.com/ditrit/badaas/mocks/controllers" + mockMiddlewares "github.com/ditrit/badaas/mocks/router/middlewares" + "github.com/ditrit/badaas/router/middlewares" +) + +var logger, _ = zap.NewDevelopment() + +func TestAddInfoRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + informationController := controllers.NewInfoController(semver.MustParse("1.0.1")) + + router := NewRouter() + AddInfoRoutes( + router, + jsonController, + informationController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/info", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"status\":\"OK\",\"version\":\"1.0.1\"}") +} + +func TestAddLoginRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + + basicAuthenticationController := mockControllers.NewBasicAuthenticationController(t) + basicAuthenticationController. + On("BasicLoginHandler", mock.Anything, mock.Anything). + Return(map[string]string{"login": "called"}, nil) + + authenticationMiddleware := mockMiddlewares.NewAuthenticationMiddleware(t) + + router := NewRouter() + AddAuthRoutes( + router, + authenticationMiddleware, + basicAuthenticationController, + jsonController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/login", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"login\":\"called\"}") +} diff --git a/scripts/e2e/api/Dockerfile b/scripts/e2e/api/Dockerfile deleted file mode 100644 index 49bdc272..00000000 --- a/scripts/e2e/api/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -RUN apk add build-base -WORKDIR /app -COPY . . -RUN CGO_ENABLED=1 go build --race -a -o badaas . - -FROM alpine:3.16.2 -ENV BADAAS_PORT=8000 -COPY --from=builder /app/badaas . -COPY ./scripts/e2e/api/badaas.yml . -ENTRYPOINT [ "./badaas" ] diff --git a/scripts/e2e/db/Dockerfile b/scripts/e2e/db/Dockerfile deleted file mode 100644 index 6f892635..00000000 --- a/scripts/e2e/db/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM cockroachdb/cockroach:latest - -LABEL maintainer="tjveil@gmail.com" - -ADD init.sh /cockroach/ -RUN chmod a+x /cockroach/init.sh - -ADD logs.yaml /cockroach/ - -WORKDIR /cockroach/ - -EXPOSE 8080 -EXPOSE 26257 - -ENTRYPOINT ["/cockroach/init.sh"] \ No newline at end of file diff --git a/scripts/e2e/db/init.sh b/scripts/e2e/db/init.sh deleted file mode 100644 index 7231f8bf..00000000 --- a/scripts/e2e/db/init.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -echo "******************************* Listing Env Variables..." -printenv -echo "******************************* starting single cockroach node..." - -./cockroach start-single-node --insecure --log-config-file=logs.yaml --background - - -echo "******************************* Creating user" -# cockroach user set ${COCKROACH_USER} --password 1234 --echo-sql -# cockroach user ls - -echo "******************************* Init database" -echo "******************************* |=> Creating init.sql" - -cat > init.sql < Applying init.sql" - -./cockroach sql --insecure --file init.sql - -echo "******************************* To the moon" - -cd /cockroach/cockroach-data/logs -tail -f cockroach.log \ No newline at end of file diff --git a/scripts/e2e/docker-compose.yml b/scripts/e2e/docker-compose.yml deleted file mode 100644 index a6b936a3..00000000 --- a/scripts/e2e/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION -version: '3.5' - -services: - db: - build: db/. - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - environment: - - COCKROACH_USER=root - - COCKROACH_DB=badaas_db - - api: - build: - context: ./../.. - dockerfile: ./scripts/e2e/api/Dockerfile - ports: - - "8000:8000" - restart: always - # environment: - # - BADAAS_PORT=8000 - # - BADAAS_MAX_TIMOUT= 15 # in seconds - depends_on: - - db diff --git a/server.go b/server.go new file mode 100644 index 00000000..e27b767d --- /dev/null +++ b/server.go @@ -0,0 +1,71 @@ +package badaas + +// This file holds functions needed by the badaas rootCommand, +// those functions help in creating the http.Server. + +import ( + "context" + "net" + "net/http" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "go.uber.org/fx" + "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" +) + +// Create the server from the configuration holder and the http handler +func createServer(handler http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { + timeout := httpServerConfig.GetMaxTimeout() + + return &http.Server{ + Handler: handler, + Addr: httpServerConfig.GetAddr(), + + WriteTimeout: timeout, + ReadTimeout: timeout, + } +} + +func newHTTPServer( + lc fx.Lifecycle, + logger *zap.Logger, + router *mux.Router, + httpServerConfig configuration.HTTPServerConfiguration, +) *http.Server { + handler := handlers.CORS( + handlers.AllowedMethods([]string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}), + handlers.AllowedHeaders([]string{ + "Accept", "Content-Type", "Content-Length", + "Accept-Encoding", "X-CSRF-Token", "Authorization", + "Access-Control-Request-Headers", "Access-Control-Request-Method", + "Connection", "Host", "Origin", "User-Agent", "Referer", + "Cache-Control", "X-header", + }), + handlers.AllowedOrigins([]string{"*"}), + handlers.AllowCredentials(), + handlers.MaxAge(0), + )(router) + + srv := createServer(handler, httpServerConfig) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + ln, err := net.Listen("tcp", srv.Addr) + if err != nil { + return err + } + logger.Sugar().Infof("Ready to serve at %s", srv.Addr) + go srv.Serve(ln) + return nil + }, + OnStop: func(ctx context.Context) error { + // Flush the logger + _ = logger.Sync() + return srv.Shutdown(ctx) + }, + }) + + return srv +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 00000000..cb231be3 --- /dev/null +++ b/server_test.go @@ -0,0 +1,19 @@ +package badaas + +// This files holds the tests for the server.go file. + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/configuration" +) + +func TestCreateServer(t *testing.T) { + handl := http.NewServeMux() + + srv := createServer(handl, configuration.NewHTTPServerConfiguration()) + assert.NotNil(t, srv) +} diff --git a/services/ModuleFx.go b/services/ModuleFx.go new file mode 100644 index 00000000..0259a5b8 --- /dev/null +++ b/services/ModuleFx.go @@ -0,0 +1,32 @@ +package services + +import ( + "go.uber.org/fx" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/services/sessionservice" + "github.com/ditrit/badaas/services/userservice" +) + +var AuthServiceModule = fx.Module( + "authService", + // models + fx.Provide(getAuthModels), + // repositories + fx.Provide(orm.NewCRUDRepository[models.Session, orm.UUID]), + fx.Provide(orm.NewCRUDRepository[models.User, orm.UUID]), + + // services + fx.Provide(userservice.NewUserService), + fx.Provide(sessionservice.NewSessionService), +) + +func getAuthModels() orm.GetModelsResult { + return orm.GetModelsResult{ + Models: []any{ + models.Session{}, + models.User{}, + }, + } +} diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index d4368319..bcf67cb6 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -2,17 +2,16 @@ package sessionservice import ( "fmt" - "net/http" "sync" "time" - "github.com/Masterminds/squirrel" + "go.uber.org/zap" + "gorm.io/gorm" + "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" - "go.uber.org/zap" ) // Errors @@ -25,10 +24,11 @@ var ( // SessionService handle sessions type SessionService interface { - IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) - RollSession(uuid.UUID) httperrors.HTTPError - LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError - LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError + IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) + // TODO services should not work with httperrors + RollSession(orm.UUID) httperrors.HTTPError + LogUserIn(user *models.User) (*models.Session, error) + LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError } // Check interface compliance @@ -36,40 +36,35 @@ var _ SessionService = (*sessionServiceImpl)(nil) // The SessionService concrete interface type sessionServiceImpl struct { - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID] - cache map[uuid.UUID]*models.Session + sessionRepository orm.CRUDRepository[models.Session, orm.UUID] + cache map[orm.UUID]*models.Session mutex sync.Mutex logger *zap.Logger sessionConfiguration configuration.SessionConfiguration + db *gorm.DB } // The SessionService constructor func NewSessionService( logger *zap.Logger, - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID], + sessionRepository orm.CRUDRepository[models.Session, orm.UUID], sessionConfiguration configuration.SessionConfiguration, + db *gorm.DB, ) SessionService { sessionService := &sessionServiceImpl{ - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[orm.UUID]*models.Session), logger: logger, sessionRepository: sessionRepository, sessionConfiguration: sessionConfiguration, + db: db, } sessionService.init() return sessionService } -// Create a new session -func newSession(userID uuid.UUID, sessionDuration time.Duration) *models.Session { - return &models.Session{ - UserID: userID, - ExpiresAt: time.Now().Add(sessionDuration), - } -} - // Return true if the session exists and is still valid. // A instance of SessionClaims is returned to be added to the request context if the conditions previously mentioned are met. -func (sessionService *sessionServiceImpl) IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) { +func (sessionService *sessionServiceImpl) IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) { sessionInstance := sessionService.get(sessionUUID) if sessionInstance == nil { return false, nil @@ -79,39 +74,45 @@ func (sessionService *sessionServiceImpl) IsValid(sessionUUID uuid.UUID) (bool, // Get a session from cache // return nil if not found -func (sessionService *sessionServiceImpl) get(sessionUUID uuid.UUID) *models.Session { +func (sessionService *sessionServiceImpl) get(sessionUUID orm.UUID) *models.Session { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session, ok := sessionService.cache[sessionUUID] if ok { return session } - sessionsFoundWithUUID, databaseError := sessionService.sessionRepository.Find(squirrel.Eq{"uuid": sessionUUID.String()}, nil, nil) - if databaseError != nil { + + session, err := sessionService.sessionRepository.GetByID( + sessionService.db, + sessionUUID, + ) + if err != nil { return nil } - if !sessionsFoundWithUUID.HasContent { - return nil // no sessions found in database - } - return sessionsFoundWithUUID.Ressources[0] + + return session } // Add a session to the cache -func (sessionService *sessionServiceImpl) add(session *models.Session) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) add(session *models.Session) error { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - herr := sessionService.sessionRepository.Create(session) - if herr != nil { - return herr + + err := sessionService.sessionRepository.Create(sessionService.db, session) + if err != nil { + return err } + sessionService.cache[session.ID] = session sessionService.logger.Debug("Added session", zap.String("uuid", session.ID.String())) + return nil } // Initialize the session service -func (sessionService *sessionServiceImpl) init() error { - sessionService.cache = make(map[uuid.UUID]*models.Session) +func (sessionService *sessionServiceImpl) init() { + sessionService.cache = make(map[orm.UUID]*models.Session) go func() { for { sessionService.removeExpired() @@ -121,18 +122,19 @@ func (sessionService *sessionServiceImpl) init() error { ) } }() - return nil } // Get all sessions and save them in cache func (sessionService *sessionServiceImpl) pullFromDB() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - sessionsFromDatabase, err := sessionService.sessionRepository.GetAll(nil) + + sessionsFromDatabase, err := sessionService.sessionRepository.Query(sessionService.db) if err != nil { panic(err) } - newSessionCache := make(map[uuid.UUID]*models.Session) + + newSessionCache := make(map[orm.UUID]*models.Session) for _, sessionFromDatabase := range sessionsFromDatabase { newSessionCache[sessionFromDatabase.ID] = sessionFromDatabase } @@ -147,11 +149,12 @@ func (sessionService *sessionServiceImpl) pullFromDB() { func (sessionService *sessionServiceImpl) removeExpired() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + var i int for sessionUUID, session := range sessionService.cache { if session.IsExpired() { // Delete the session in the database - err := sessionService.sessionRepository.Delete(session) + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { panic(err) } @@ -172,8 +175,9 @@ func (sessionService *sessionServiceImpl) removeExpired() { func (sessionService *sessionServiceImpl) delete(session *models.Session) httperrors.HTTPError { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + sessionUUID := session.ID - err := sessionService.sessionRepository.Delete(session) + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { return httperrors.NewInternalServerError( "session error", @@ -186,7 +190,7 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper } // Roll a session. If the session is close to expiration, extend its duration. -func (sessionService *sessionServiceImpl) RollSession(sessionUUID uuid.UUID) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) httperrors.HTTPError { rollInterval := sessionService.sessionConfiguration.GetRollDuration() sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() session := sessionService.get(sessionUUID) @@ -194,64 +198,51 @@ func (sessionService *sessionServiceImpl) RollSession(sessionUUID uuid.UUID) htt // no session to roll, no error return nil } + if session.IsExpired() { return HERRSessionExpired } + if session.CanBeRolled(rollInterval) { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session.ExpiresAt = session.ExpiresAt.Add(sessionDuration) - herr := sessionService.sessionRepository.Save(session) - if herr != nil { - return herr + err := sessionService.sessionRepository.Save(sessionService.db, session) + if err != nil { + return httperrors.NewDBError(err) } + sessionService.logger.Warn("Rolled session", zap.String("userID", session.UserID.String()), zap.String("sessionID", session.ID.String())) } + return nil } // Log in a user -func (sessionService *sessionServiceImpl) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserIn(user *models.User) (*models.Session, error) { sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() - session := newSession(user.ID, sessionDuration) + session := models.NewSession(user.ID, sessionDuration) err := sessionService.add(session) if err != nil { - return err + return nil, err } - CreateAndSetAccessTokenCookie(response, session.ID.String()) - return nil + return session, nil } // Log out a user. -func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError { session := sessionService.get(sessionClaims.SessionUUID) if session == nil { - return httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") + return httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") } + err := sessionService.delete(session) if err != nil { return err } - CreateAndSetAccessTokenCookie(response, "") - return nil -} -// Create an access token and send it in a cookie -func CreateAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) { - accessToken := &http.Cookie{ - Name: "access_token", - Path: "/", - Value: sessionUUID, - HttpOnly: true, - SameSite: http.SameSiteNoneMode, // change to http.SameSiteStrictMode in prod - Secure: false, // change to true in prod - Expires: time.Now().Add(48 * time.Hour), - } - err := accessToken.Valid() - if err != nil { - panic(err) - } - http.SetCookie(w, accessToken) + return nil } diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index 422b831f..42bf0b02 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -1,83 +1,82 @@ package sessionservice import ( - "net/http/httptest" + "errors" "testing" "time" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" -) + "gorm.io/gorm" -func TestNewSession(t *testing.T) { - sessionInstance := newSession(uuid.Nil, time.Second) - assert.NotNil(t, sessionInstance) - assert.Equal(t, uuid.Nil, sessionInstance.UserID) -} + "github.com/ditrit/badaas/httperrors" + configurationMocks "github.com/ditrit/badaas/mocks/configuration" + ormMocks "github.com/ditrit/badaas/mocks/orm" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" +) -func TestLogInUser(t *testing.T) { - sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() - user := &models.User{ - Username: "bob", - Email: "bob@email.com", - } - err := service.LogUserIn(user, response) - require.NoError(t, err) - assert.Len(t, service.cache, 1) - assert.Equal(t, 1, logs.Len()) - log := logs.All()[0] - assert.Equal(t, "Added session", log.Message) - require.Len(t, log.Context, 1) -} +var gormDB *gorm.DB // make values for test func setupTest( t *testing.T, ) ( - *repositorymocks.CRUDRepository[models.Session, uuid.UUID], + *ormMocks.CRUDRepository[models.Session, orm.UUID], *sessionServiceImpl, *observer.ObservedLogs, - *configurationmocks.SessionConfiguration, + *configurationMocks.SessionConfiguration, ) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - sessionRepositoryMock := repositorymocks.NewCRUDRepository[models.Session, uuid.UUID](t) - sessionConfiguration := configurationmocks.NewSessionConfiguration(t) + sessionRepositoryMock := ormMocks.NewCRUDRepository[models.Session, orm.UUID](t) + sessionConfiguration := configurationMocks.NewSessionConfiguration(t) service := &sessionServiceImpl{ sessionRepository: sessionRepositoryMock, logger: logger, - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[orm.UUID]*models.Session), sessionConfiguration: sessionConfiguration, + db: gormDB, } return sessionRepositoryMock, service, logs, sessionConfiguration } +func TestLogInUser(t *testing.T) { + sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) + user := &models.User{ + Username: "bob", + Email: "bob@email.com", + } + _, err := service.LogUserIn(user) + require.NoError(t, err) + assert.Len(t, service.cache, 1) + assert.Equal(t, 1, logs.Len()) + log := logs.All()[0] + assert.Equal(t, "Added session", log.Message) + require.Len(t, log.Context, 1) +} + func TestLogInUserDbError(t *testing.T) { sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(httperrors.NewInternalServerError("db err", "nil", nil)) + sessionRepositoryMock. + On("Create", gormDB, mock.Anything). + Return(errors.New("db err")) + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() user := &models.User{ Username: "bob", Email: "bob@email.com", } - err := service.LogUserIn(user, response) + _, err := service.LogUserIn(user) require.Error(t, err) assert.Len(t, service.cache, 0) assert.Equal(t, 0, logs.Len()) @@ -85,23 +84,23 @@ func TestLogInUserDbError(t *testing.T) { func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - uuidSample := uuid.New() + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } err := service.add(session) require.NoError(t, err) assert.Len(t, service.cache, 1) - assert.Equal(t, uuid.Nil, service.cache[uuidSample].UserID) + assert.Equal(t, orm.NilUUID, service.cache[uuidSample].UserID) isValid, claims := service.IsValid(uuidSample) require.True(t, isValid) assert.Equal(t, *claims, SessionClaims{ - UserID: uuid.Nil, + UserID: orm.NilUUID, SessionUUID: uuidSample, }) } @@ -109,46 +108,46 @@ func TestIsValid(t *testing.T) { func TestIsValid_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, mock.Anything, mock.Anything). - Return(pagination.NewPage([]*models.Session{}, 0, 125, 1236), nil) - uuidSample := uuid.New() + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) + uuidSample := orm.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) - // } func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(nil) - response := httptest.NewRecorder() - uuidSample := uuid.New() + sessionRepositoryMock.On("Delete", gormDB, mock.Anything).Return(nil) + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.NoError(t, err) assert.Len(t, service.cache, 0) } func TestLogOutUserDbError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() - uuidSample := uuid.New() + sessionRepositoryMock. + On("Delete", gormDB, mock.Anything). + Return(errors.New("db errors")) + uuidSample := orm.NewUUID() + session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.Error(t, err) assert.Len(t, service.cache, 1) } @@ -156,38 +155,38 @@ func TestLogOutUserDbError(t *testing.T) { func TestLogOutUser_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(nil, httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() - uuidSample := uuid.New() + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) + + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session sessionClaims := makeSessionClaims(session) - sessionClaims.SessionUUID = uuid.Nil - err := service.LogUserOut(sessionClaims, response) + sessionClaims.SessionUUID = orm.NilUUID + err := service.LogUserOut(sessionClaims) require.Error(t, err) assert.Len(t, service.cache, 1) } func TestRollSession(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Save", mock.Anything).Return(nil) + sessionRepositoryMock.On("Save", gormDB, mock.Anything).Return(nil) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(sessionDuration / 5) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -201,13 +200,13 @@ func TestRollSession_Expired(t *testing.T) { sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -221,46 +220,49 @@ func TestRollSession_falseUUID(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session - repoSession.On("Find", mock.Anything, nil, nil).Return(pagination.NewPage([]*models.Session{}, 0, 2, 5), nil) - err := service.RollSession(uuid.New()) + + repoSession. + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) + + err := service.RollSession(orm.NewUUID()) require.NoError(t, err) } func TestRollSession_sessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) sessionRepositoryMock. - On("Find", squirrel.Eq{"uuid": "00000000-0000-0000-0000-000000000000"}, nil, nil). - Return( - pagination.NewPage([]*models.Session{}, 0, 10, 0), nil) + On("GetByID", gormDB, orm.NilUUID). + Return(nil, errors.New("not-found")) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration) - err := service.RollSession(uuid.Nil) + err := service.RollSession(orm.NilUUID) require.NoError(t, err) } func Test_pullFromDB(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } - sessionRepositoryMock.On("GetAll", nil).Return([]*models.Session{session}, nil) + sessionRepositoryMock.On("Query", gormDB).Return([]*models.Session{session}, nil) service.pullFromDB() assert.Len(t, service.cache, 1) @@ -274,22 +276,22 @@ func Test_pullFromDB(t *testing.T) { func Test_pullFromDB_repoError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("GetAll", nil).Return(nil, httperrors.AnError) + sessionRepositoryMock.On("Query", gormDB).Return(nil, httperrors.AnError) assert.PanicsWithError(t, httperrors.AnError.Error(), func() { service.pullFromDB() }) } func Test_removeExpired(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). + On("Delete", gormDB, session). Return(nil) service.cache[uuidSample] = session @@ -305,16 +307,16 @@ func Test_removeExpired(t *testing.T) { func Test_removeExpired_RepositoryError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). + On("Delete", gormDB, session). Return(httperrors.AnError) service.cache[uuidSample] = session @@ -323,17 +325,17 @@ func Test_removeExpired_RepositoryError(t *testing.T) { func Test_get(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(pagination.NewPage([]*models.Session{session}, 0, 12, 13), nil) + On("GetByID", gormDB, mock.Anything). + Return(session, nil) sessionFound := service.get(uuidSample) assert.Equal(t, sessionFound, session) diff --git a/services/sessionservice/sessionctx.go b/services/sessionservice/sessionctx.go index 6710766a..ec7a70ac 100644 --- a/services/sessionservice/sessionctx.go +++ b/services/sessionservice/sessionctx.go @@ -3,14 +3,14 @@ package sessionservice import ( "context" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" - "github.com/google/uuid" ) // The session claims passed in the request context type SessionClaims struct { - UserID uuid.UUID - SessionUUID uuid.UUID + UserID orm.UUID + SessionUUID orm.UUID } // Unique claim key type diff --git a/services/sessionservice/sessionctx_test.go b/services/sessionservice/sessionctx_test.go index 88b3f954..129dc101 100644 --- a/services/sessionservice/sessionctx_test.go +++ b/services/sessionservice/sessionctx_test.go @@ -6,14 +6,16 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" ) func TestSessionCtx(t *testing.T) { ctx := context.Background() - sessionClaims := &SessionClaims{uuid.Nil, uuid.New()} + sessionClaims := &SessionClaims{orm.NilUUID, orm.UUID(uuid.New())} ctx = SetSessionClaimsContext(ctx, sessionClaims) claims := GetSessionClaimsFromContext(ctx) - assert.Equal(t, uuid.Nil, claims.UserID) + assert.Equal(t, orm.NilUUID, claims.UserID) } func TestSessionCtxPanic(t *testing.T) { diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 3f3da399..24387de2 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -1,42 +1,47 @@ package userservice import ( + "errors" "fmt" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/repository" "github.com/ditrit/badaas/services/auth/protocols/basicauth" validator "github.com/ditrit/badaas/validators" - "github.com/google/uuid" - "go.uber.org/zap" ) // UserService provide functions related to Users type UserService interface { NewUser(username, email, password string) (*models.User, error) - GetUser(dto.UserLoginDTO) (*models.User, httperrors.HTTPError) + GetUser(dto.UserLoginDTO) (*models.User, error) } +var ErrWrongPassword = errors.New("password is incorrect") + // Check interface compliance var _ UserService = (*userServiceImpl)(nil) // The UserService concrete implementation type userServiceImpl struct { - userRepository repository.CRUDRepository[models.User, uuid.UUID] + userRepository orm.CRUDRepository[models.User, orm.UUID] logger *zap.Logger + db *gorm.DB } // UserService constructor func NewUserService( logger *zap.Logger, - userRepository repository.CRUDRepository[models.User, uuid.UUID], + userRepository orm.CRUDRepository[models.User, orm.UUID], + db *gorm.DB, ) UserService { return &userServiceImpl{ logger: logger, userRepository: userRepository, + db: db, } } @@ -46,37 +51,40 @@ func (userService *userServiceImpl) NewUser(username, email, password string) (* if err != nil { return nil, fmt.Errorf("the provided email is not valid") } + u := &models.User{ Username: username, Email: sanitizedEmail, Password: basicauth.SaltAndHashPassword(password), } - httpError := userService.userRepository.Create(u) - if httpError != nil { - return nil, httpError + err = userService.userRepository.Create(userService.db, u) + if err != nil { + return nil, err } - userService.logger.Info("Successfully created a new user", - zap.String("email", sanitizedEmail), zap.String("username", username)) + + userService.logger.Info( + "Successfully created a new user", + zap.String("email", sanitizedEmail), + zap.String("username", username), + ) return u, nil } // Get user if the email and password provided are correct, return an error if not. -func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { - users, herr := userService.userRepository.Find(squirrel.Eq{"email": userLoginDTO.Email}, nil, nil) - if herr != nil { - return nil, herr - } - if !users.HasContent { - return nil, httperrors.NewErrorNotFound("user", - fmt.Sprintf("no user found with email %q", userLoginDTO.Email)) +func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, error) { + user, err := userService.userRepository.QueryOne( + userService.db, + models.UserEmailCondition(orm.Eq(userLoginDTO.Email)), + ) + if err != nil { + return nil, err } - user := users.Ressources[0] - // Check password if !basicauth.CheckUserPassword(user.Password, userLoginDTO.Password) { - return nil, httperrors.NewUnauthorizedError("wrong password", "the provided password is incorrect") + return nil, ErrWrongPassword } + return user, nil } diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index df435ed8..e76cc2cf 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -1,31 +1,35 @@ package userservice_test import ( + "errors" "testing" - "github.com/ditrit/badaas/httperrors" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/ditrit/badaas/services/userservice" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" + + ormMocks "github.com/ditrit/badaas/mocks/orm" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/persistence/models/dto" + "github.com/ditrit/badaas/services/userservice" ) +var gormDB *gorm.DB + func TestNewUserService(t *testing.T) { // creating logger observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On("Create", mock.Anything).Return(nil) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.NoError(t, err) assert.NotNil(t, user) @@ -49,13 +53,13 @@ func TestNewUserServiceDatabaseError(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, ).Return( - httperrors.NewInternalServerError("database error", "test error", nil), + errors.New("database error"), ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -69,9 +73,9 @@ func TestNewUserServiceEmailNotValid(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -85,20 +89,19 @@ func TestGetUser(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, + ).Return(nil) + user, err := userService.NewUser("bob", "bob@email.com", "1234") require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bob@email.com")), ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), + user, nil, ) @@ -121,13 +124,13 @@ func TestGetUserNoUserFound(t *testing.T) { observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bobnotfound@email.com")), ).Return( - nil, - httperrors.NewErrorNotFound("user", "user with email bobnotfound@email.com"), + &models.User{}, + orm.ErrObjectNotFound, ) userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bobnotfound@email.com", Password: "1234"}) @@ -136,53 +139,24 @@ func TestGetUserNoUserFound(t *testing.T) { } // Check what happen if the pass word is not correct -func TestGetUserNotCorrect(t *testing.T) { +func TestGetUserWrongPassword(t *testing.T) { // creating logger observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - user, err := userService.NewUser("bob", "bob@email.com", "1234") + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, + ).Return(nil) - require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, - ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), - nil, - ) - - userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bob@email.com", Password: " ../ + +require ( + github.com/Masterminds/semver/v3 v3.1.1 + github.com/cucumber/godog v0.12.5 + github.com/ditrit/badaas v0.0.0 + github.com/elliotchance/pie/v2 v2.7.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.16.0 + go.uber.org/zap v1.24.0 + gorm.io/gorm v1.25.1 +) + +require ( + github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect + github.com/cucumber/messages-go/v16 v16.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ditrit/verdeter v0.4.0 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.3 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/fx v1.19.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gotest.tools v2.2.0+incompatible // indirect +) diff --git a/test_e2e/go.sum b/test_e2e/go.sum new file mode 100644 index 00000000..c0628486 --- /dev/null +++ b/test_e2e/go.sum @@ -0,0 +1,829 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= +github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= +github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= +github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= +github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= +github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= +github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= +github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= +github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/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/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/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-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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-20181221193216-37e7f081c4d4/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/http_support_test.go b/test_e2e/http_support_test.go similarity index 59% rename from http_support_test.go rename to test_e2e/http_support_test.go index b1f5ec7c..a53ab405 100644 --- a/http_support_test.go +++ b/test_e2e/http_support_test.go @@ -1,26 +1,56 @@ package main import ( - "context" "encoding/json" "fmt" "io" - "log" "net/http" "strconv" "strings" "github.com/cucumber/godog" + "github.com/elliotchance/pie/v2" ) const BaseUrl = "http://localhost:8000" -func (t *TestContext) requestGET(url string) error { - response, err := t.httpClient.Get(fmt.Sprintf("%s%s", BaseUrl, url)) +func (t *TestContext) requestGet(url string) error { + return t.request(url, http.MethodGet, nil, nil) +} + +func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { + return t.request(url, method, nil, jsonTable) +} + +func (t *TestContext) request(url, method string, query map[string]string, jsonTable *godog.Table) error { + var payload io.Reader + var err error + if jsonTable != nil { + payload, err = buildJSONFromTable(jsonTable) + if err != nil { + return err + } + } + + method, err = checkMethod(method) if err != nil { return err } + request, err := http.NewRequest(method, BaseUrl+url, payload) + if err != nil { + return fmt.Errorf("failed to build request ERROR=%s", err.Error()) + } + q := request.URL.Query() + for k, v := range query { + q.Add(k, v) + } + request.URL.RawQuery = q.Encode() + + response, err := t.httpClient.Do(request) + if err != nil { + return fmt.Errorf("failed to run request ERROR=%s", err.Error()) + } t.storeResponseInContext(response) return nil } @@ -28,56 +58,66 @@ func (t *TestContext) requestGET(url string) error { func (t *TestContext) storeResponseInContext(response *http.Response) { t.statusCode = response.StatusCode - buffer, err := io.ReadAll(response.Body) + err := json.NewDecoder(response.Body).Decode(&t.json) if err != nil { - log.Panic(err) + t.json = map[string]any{} } - response.Body.Close() - json.Unmarshal(buffer, &t.json) } -func (t *TestContext) assertStatusCode(_ context.Context, expectedStatusCode int) error { +func (t *TestContext) assertStatusCode(expectedStatusCode int) error { if t.statusCode != expectedStatusCode { return fmt.Errorf("expect status code %d but is %d", expectedStatusCode, t.statusCode) } return nil } -func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { - value := t.json[field].(string) - if !assertValue(value, expectedValue) { - return fmt.Errorf("expect response field %s is %s but is %s", field, expectedValue, value) - } - return nil -} -func assertValue(value string, expectedValue string) bool { - return expectedValue == value -} +func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { + fields := strings.Split(field, ".") + jsonMap := t.json.(map[string]any) -func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { - payload, err := buildJSONFromTable(jsonTable) - if err != nil { - return err + for _, field := range fields[:len(fields)-1] { + intValue, present := jsonMap[field] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) + } + jsonMap = intValue.(map[string]any) } - method, err = checkMethod(method) - if err != nil { - return err + lastValue, present := jsonMap[pie.Last(fields)] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) } - request, err := http.NewRequest(method, BaseUrl+url, payload) - if err != nil { - return fmt.Errorf("failed to build request ERROR=%s", err.Error()) - } - response, err := t.httpClient.Do(request) - if err != nil { - return fmt.Errorf("failed to run request ERROR=%s", err.Error()) + + if !assertValue(lastValue, expectedValue) { + return fmt.Errorf("expected response field %s to be %s but is %v", field, expectedValue, lastValue) } - t.storeResponseInContext(response) + return nil } -// build a json payload in the form of a reader from a godog.Table -func buildJSONFromTable(table *godog.Table) (io.Reader, error) { +func assertValue(value any, expectedValue string) bool { + switch value.(type) { + case string: + return expectedValue == value + case int: + expectedValueInt, err := strconv.Atoi(expectedValue) + if err != nil { + panic(err) + } + return expectedValueInt == value + case float64: + expectedValueFloat, err := strconv.ParseFloat(expectedValue, 64) + if err != nil { + panic(err) + } + return expectedValueFloat == value + default: + panic("unsupported format") + } +} + +// build a map from a godog.Table +func buildMapFromTable(table *godog.Table) (map[string]any, error) { data := make(map[string]any, 0) for indexRow, row := range table.Rows { if indexRow == 0 { @@ -112,6 +152,13 @@ func buildJSONFromTable(table *godog.Table) (io.Reader, error) { return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) } data[key] = floatingNumber + case jsonValueType: + jsonMap := map[string]string{} + err := json.Unmarshal([]byte(valueAsString), &jsonMap) + if err != nil { + return nil, fmt.Errorf("can't parse %q as json for key %q", valueAsString, key) + } + data[key] = jsonMap case nullValueType: data[key] = nil default: @@ -120,6 +167,17 @@ func buildJSONFromTable(table *godog.Table) (io.Reader, error) { } } + + return data, nil +} + +// build a json payload in the form of a reader from a godog.Table +func buildJSONFromTable(table *godog.Table) (io.Reader, error) { + data, err := buildMapFromTable(table) + if err != nil { + panic("should not return an error") + } + bytes, err := json.Marshal(data) if err != nil { panic("should not return an error") @@ -133,11 +191,13 @@ const ( integerValueType = "integer" floatValueType = "float" nullValueType = "null" + jsonValueType = "json" ) // check if the method is allowed and sanitize the string func checkMethod(method string) (string, error) { - allowedMethods := []string{http.MethodGet, + allowedMethods := []string{ + http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, @@ -145,24 +205,15 @@ func checkMethod(method string) (string, error) { http.MethodDelete, http.MethodConnect, http.MethodOptions, - http.MethodTrace} + http.MethodTrace, + } sanitizedMethod := strings.TrimSpace(strings.ToUpper(method)) - if !contains( + if !pie.Contains( allowedMethods, sanitizedMethod, ) { return "", fmt.Errorf("%q is not a valid HTTP method (please choose between %v)", method, allowedMethods) } - return sanitizedMethod, nil - -} -// return true if the set contains the target -func contains[T comparable](set []T, target T) bool { - for _, elem := range set { - if target == elem { - return true - } - } - return false + return sanitizedMethod, nil } diff --git a/test_e2e/setup.go b/test_e2e/setup.go new file mode 100644 index 00000000..8339f29a --- /dev/null +++ b/test_e2e/setup.go @@ -0,0 +1,17 @@ +package main + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/testintegration" +) + +func CleanDB(db *gorm.DB) { + testintegration.CleanDBTables(db, + []any{ + models.Session{}, + models.User{}, + }, + ) +} diff --git a/test_e2e/test_api.go b/test_e2e/test_api.go new file mode 100644 index 00000000..c8ab617d --- /dev/null +++ b/test_e2e/test_api.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/Masterminds/semver/v3" + + "github.com/ditrit/badaas" +) + +func main() { + badaas.BaDaaS.AddModules( + badaas.InfoModule, + badaas.AuthModule, + ).Provide( + NewAPIVersion, + ).Start() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} diff --git a/testintegration/asserts.go b/testintegration/asserts.go new file mode 100644 index 00000000..b0c58e98 --- /dev/null +++ b/testintegration/asserts.go @@ -0,0 +1,33 @@ +package testintegration + +import ( + "log" + + "github.com/stretchr/testify/suite" + is "gotest.tools/assert/cmp" +) + +func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { + expectedLen := len(expectedList) + equalLen := ts.Len(actualList, expectedLen) + + if equalLen { + for i := 0; i < expectedLen; i++ { + j := 0 + for ; j < expectedLen; j++ { + if is.DeepEqual( + actualList[j], + expectedList[i], + )().Success() { + break + } + } + if j == expectedLen { + ts.Fail("Lists not equal", "element %v not in list %v", expectedList[i], actualList) + for _, element := range actualList { + log.Println(element) + } + } + } + } +} diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go new file mode 100644 index 00000000..b16e5773 --- /dev/null +++ b/testintegration/conditions/bicycle_conditions.go @@ -0,0 +1,52 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func BicycleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func BicycleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func BicycleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func BicycleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, string]{ + Field: "Name", + Operator: operator, + } +} +func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { + return orm.JoinCondition[models.Bicycle, models.Person]{ + Conditions: conditions, + T1Field: "OwnerName", + T2Field: "Name", + } +} +func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, string]{ + Field: "OwnerName", + Operator: operator, + } +} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go new file mode 100644 index 00000000..9d3421ab --- /dev/null +++ b/testintegration/conditions/brand_conditions.go @@ -0,0 +1,39 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func BrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, uint]{ + Field: "ID", + Operator: operator, + } +} +func BrandCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func BrandUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func BrandDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, string]{ + Field: "Name", + Operator: operator, + } +} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go new file mode 100644 index 00000000..1f0218ff --- /dev/null +++ b/testintegration/conditions/city_conditions.go @@ -0,0 +1,45 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func CityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func CityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func CityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func CityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, string]{ + Field: "Name", + Operator: operator, + } +} +func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, orm.UUID]{ + Field: "CountryID", + Operator: operator, + } +} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go new file mode 100644 index 00000000..b87db9b2 --- /dev/null +++ b/testintegration/conditions/company_conditions.go @@ -0,0 +1,46 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func CompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func CompanyCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func CompanyUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func CompanyDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, string]{ + Field: "Name", + Operator: operator, + } +} +func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { + return orm.JoinCondition[models.Seller, models.Company]{ + Conditions: conditions, + T1Field: "CompanyID", + T2Field: "ID", + } +} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go new file mode 100644 index 00000000..f00fddf4 --- /dev/null +++ b/testintegration/conditions/country_conditions.go @@ -0,0 +1,53 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func CountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func CountryCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func CountryUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func CountryDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, string]{ + Field: "Name", + Operator: operator, + } +} +func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { + return orm.JoinCondition[models.Country, models.City]{ + Conditions: conditions, + T1Field: "ID", + T2Field: "CountryID", + } +} +func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { + return orm.JoinCondition[models.City, models.Country]{ + Conditions: conditions, + T1Field: "CountryID", + T2Field: "ID", + } +} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go new file mode 100644 index 00000000..77860234 --- /dev/null +++ b/testintegration/conditions/employee_conditions.go @@ -0,0 +1,52 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func EmployeeId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func EmployeeCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func EmployeeUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func EmployeeDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, string]{ + Field: "Name", + Operator: operator, + } +} +func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { + return orm.JoinCondition[models.Employee, models.Employee]{ + Conditions: conditions, + T1Field: "BossID", + T2Field: "ID", + } +} +func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, orm.UUID]{ + Field: "BossID", + Operator: operator, + } +} diff --git a/testintegration/conditions/orm.go b/testintegration/conditions/orm.go new file mode 100644 index 00000000..78acbc6d --- /dev/null +++ b/testintegration/conditions/orm.go @@ -0,0 +1,3 @@ +package conditions + +//go:generate badaas-cli gen conditions ../models diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go new file mode 100644 index 00000000..c378abe7 --- /dev/null +++ b/testintegration/conditions/person_conditions.go @@ -0,0 +1,39 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func PersonId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func PersonCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func PersonUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func PersonDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, string]{ + Field: "Name", + Operator: operator, + } +} diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go new file mode 100644 index 00000000..7766e086 --- /dev/null +++ b/testintegration/conditions/phone_conditions.go @@ -0,0 +1,52 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func PhoneId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, uint]{ + Field: "ID", + Operator: operator, + } +} +func PhoneCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func PhoneUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func PhoneDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, string]{ + Field: "Name", + Operator: operator, + } +} +func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { + return orm.JoinCondition[models.Phone, models.Brand]{ + Conditions: conditions, + T1Field: "BrandID", + T2Field: "ID", + } +} +func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, uint]{ + Field: "BrandID", + Operator: operator, + } +} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go new file mode 100644 index 00000000..55f044fa --- /dev/null +++ b/testintegration/conditions/product_conditions.go @@ -0,0 +1,100 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func ProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func ProductCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func ProductUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func ProductDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func ProductString(operator orm.Operator[string]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, string]{ + Column: "string_something_else", + Operator: operator, + } +} +func ProductInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "Int", + Operator: operator, + } +} +func ProductIntPointer(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "IntPointer", + Operator: operator, + } +} +func ProductFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, float64]{ + Field: "Float", + Operator: operator, + } +} +func ProductNullFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, float64]{ + Field: "NullFloat", + Operator: operator, + } +} +func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, bool]{ + Field: "Bool", + Operator: operator, + } +} +func ProductNullBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, bool]{ + Field: "NullBool", + Operator: operator, + } +} +func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, []uint8]{ + Field: "ByteArray", + Operator: operator, + } +} +func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, models.MultiString]{ + Field: "MultiString", + Operator: operator, + } +} +func ProductEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "EmbeddedInt", + Operator: operator, + } +} +func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + ColumnPrefix: "gorm_embedded_", + Field: "Int", + Operator: operator, + } +} diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go new file mode 100644 index 00000000..6e49ebcf --- /dev/null +++ b/testintegration/conditions/sale_conditions.go @@ -0,0 +1,71 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func SaleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func SaleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func SaleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func SaleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func SaleCode(operator orm.Operator[int]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, int]{ + Field: "Code", + Operator: operator, + } +} +func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, string]{ + Field: "Description", + Operator: operator, + } +} +func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { + return orm.JoinCondition[models.Sale, models.Product]{ + Conditions: conditions, + T1Field: "ProductID", + T2Field: "ID", + } +} +func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "ProductID", + Operator: operator, + } +} +func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { + return orm.JoinCondition[models.Sale, models.Seller]{ + Conditions: conditions, + T1Field: "SellerID", + T2Field: "ID", + } +} +func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "SellerID", + Operator: operator, + } +} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go new file mode 100644 index 00000000..5390f2d5 --- /dev/null +++ b/testintegration/conditions/seller_conditions.go @@ -0,0 +1,45 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + "time" +) + +func SellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, orm.UUID]{ + Field: "ID", + Operator: operator, + } +} +func SellerCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "CreatedAt", + Operator: operator, + } +} +func SellerUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "UpdatedAt", + Operator: operator, + } +} +func SellerDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "DeletedAt", + Operator: operator, + } +} +func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, string]{ + Field: "Name", + Operator: operator, + } +} +func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, orm.UUID]{ + Field: "CompanyID", + Operator: operator, + } +} diff --git a/testintegration/crudRepository.go b/testintegration/crudRepository.go new file mode 100644 index 00000000..f81bed76 --- /dev/null +++ b/testintegration/crudRepository.go @@ -0,0 +1,96 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDRepositoryIntTestSuite struct { + suite.Suite + db *gorm.DB + crudProductRepository orm.CRUDRepository[models.Product, orm.UUID] +} + +func NewCRUDRepositoryIntTestSuite( + db *gorm.DB, + crudProductRepository orm.CRUDRepository[models.Product, orm.UUID], +) *CRUDRepositoryIntTestSuite { + return &CRUDRepositoryIntTestSuite{ + db: db, + crudProductRepository: crudProductRepository, + } +} + +func (ts *CRUDRepositoryIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDRepositoryIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +// ------------------------- GetByID -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsErrorIfIDDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.GetByID(ts.db, orm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsEntityIfIDMatch() { + product := ts.createProduct(0) + ts.createProduct(0) + productReturned, err := ts.crudProductRepository.GetByID(ts.db, product.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +// ------------------------- QueryOne -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfConditionsDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(1)), + ) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsEntityIfConditionsMatch() { + product := ts.createProduct(1) + productReturned, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(1)), + ) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfMoreThanOneMatchConditions() { + ts.createProduct(0) + ts.createProduct(0) + _, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(0)), + ) + ts.Error(err, orm.ErrMoreThanOneObjectFound) +} + +// ------------------------- utils ------------------------- + +func (ts *CRUDRepositoryIntTestSuite) createProduct(intV int) *models.Product { + entity := &models.Product{ + Int: intV, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go new file mode 100644 index 00000000..4769ac3b --- /dev/null +++ b/testintegration/crudServiceCommon.go @@ -0,0 +1,127 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDServiceCommonIntTestSuite struct { + suite.Suite + db *gorm.DB +} + +func (ts *CRUDServiceCommonIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) createProduct(stringV string, intV int, floatV float64, boolV bool, intP *int) *models.Product { + entity := &models.Product{ + String: stringV, + Int: intV, + Float: floatV, + Bool: boolV, + IntPointer: intP, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSale(code int, product *models.Product, seller *models.Seller) *models.Sale { + entity := &models.Sale{ + Code: code, + Product: *product, + Seller: seller, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSeller(name string, company *models.Company) *models.Seller { + var companyID *orm.UUID + if company != nil { + companyID = &company.ID + } + entity := &models.Seller{ + Name: name, + CompanyID: companyID, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCompany(name string) *models.Company { + entity := &models.Company{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCountry(name string, capital models.City) *models.Country { + entity := &models.Country{ + Name: name, + Capital: capital, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createEmployee(name string, boss *models.Employee) *models.Employee { + entity := &models.Employee{ + Name: name, + Boss: boss, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBicycle(name string, owner models.Person) *models.Bicycle { + entity := &models.Bicycle{ + Name: name, + Owner: owner, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBrand(name string) *models.Brand { + entity := &models.Brand{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createPhone(name string, brand models.Brand) *models.Phone { + entity := &models.Phone{ + Name: name, + Brand: brand, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/db_models.go b/testintegration/db_models.go new file mode 100644 index 00000000..2c24b637 --- /dev/null +++ b/testintegration/db_models.go @@ -0,0 +1,45 @@ +package testintegration + +import ( + "log" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +var ListOfTables = []any{ + models.Product{}, + models.Company{}, + models.Seller{}, + models.Sale{}, + models.Country{}, + models.City{}, + models.Employee{}, + models.Person{}, + models.Bicycle{}, + models.Brand{}, + models.Phone{}, +} + +func GetModels() orm.GetModelsResult { + return orm.GetModelsResult{ + Models: ListOfTables, + } +} + +func CleanDB(db *gorm.DB) { + CleanDBTables(db, pie.Reverse(ListOfTables)) +} + +func CleanDBTables(db *gorm.DB, listOfTables []any) { + // clean database to ensure independency between tests + for _, table := range listOfTables { + err := db.Unscoped().Where("1 = 1").Delete(table).Error + if err != nil { + log.Fatalln("could not clean database: ", err) + } + } +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go new file mode 100644 index 00000000..71d21038 --- /dev/null +++ b/testintegration/join_conditions_test.go @@ -0,0 +1,406 @@ +package testintegration + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type JoinConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudSellerService orm.CRUDService[models.Seller, orm.UUID] + crudCountryService orm.CRUDService[models.Country, orm.UUID] + crudCityService orm.CRUDService[models.City, orm.UUID] + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] + crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID] + crudPhoneService orm.CRUDService[models.Phone, uint] +} + +func NewJoinConditionsIntTestSuite( + db *gorm.DB, + crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudSellerService orm.CRUDService[models.Seller, orm.UUID], + crudCountryService orm.CRUDService[models.Country, orm.UUID], + crudCityService orm.CRUDService[models.City, orm.UUID], + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], + crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID], + crudPhoneService orm.CRUDService[models.Phone, uint], +) *JoinConditionsIntTestSuite { + return &JoinConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudSaleService: crudSaleService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudBicycleService: crudBicycleService, + crudPhoneService: crudPhoneService, + } +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsUintBelongsTo() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + match := ts.createPhone("pixel", *brand1) + ts.createPhone("iphone", *brand2) + + entities, err := ts.crudPhoneService.Query( + conditions.PhoneBrand( + conditions.BrandName(orm.Eq("google")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsBelongsTo() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(orm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersTheMainEntity() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(1, product1, seller1) + ts.createSale(2, product2, seller2) + ts.createSale(2, product1, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleCode(orm.Eq(1)), + conditions.SaleProduct( + conditions.ProductInt(orm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneOptional() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName(orm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + boss2 := &models.Employee{ + Name: "Vincent", + } + + match := ts.createEmployee("franco", boss1) + ts.createEmployee("pierre", boss2) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeeBoss( + conditions.EmployeeName(orm.Eq("Xavier")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCityService.Query( + conditions.CityCountry( + conditions.CountryName(orm.Eq("Argentina")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCountryService.Query( + conditions.CountryCapital( + conditions.CityName(orm.Eq("Buenos Aires")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsWithEntityThatDefinesTableName() { + person1 := models.Person{ + Name: "franco", + } + person2 := models.Person{ + Name: "xavier", + } + + match := ts.createBicycle("BMX", person1) + ts.createBicycle("Shimano", person2) + + entities, err := ts.crudBicycleService.Query( + conditions.BicycleOwner( + conditions.PersonName(orm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Bicycle{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnHasMany() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + match := ts.createSeller("franco", company1) + ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerCompany( + conditions.CompanyName(orm.Eq("ditrit")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDifferentAttributes() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(orm.Eq(1)), + conditions.ProductString(orm.Eq("match")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAddsDeletedAtAutomatically() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product2).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductString(orm.Eq("match")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDeletedAt() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product1).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductDeletedAt(orm.Eq(product1.DeletedAt.Time)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersByNil() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + intProduct2 := 2 + product2 := ts.createProduct("", 2, 0.0, false, &intProduct2) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductIntPointer(orm.IsNull[int]()), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsDifferentEntities() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(orm.Eq(1)), + ), + conditions.SaleSeller( + conditions.SellerName(orm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName(orm.Eq("franco")), + conditions.SellerCompany( + conditions.CompanyName(orm.Eq("ditrit")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestJoinWithUnsafeCondition() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerCompany( + orm.NewUnsafeCondition[models.Company]("%s.name = sellers_sales.name", []any{}), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match1 := ts.createSale(0, product1, nil) + match2 := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + orm.And[models.Product](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturnsError() { + _, err := ts.crudSaleService.Query( + conditions.SaleProduct( + orm.Not[models.Product](), + ), + ) + ts.ErrorIs(err, orm.ErrEmptyConditions) +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go new file mode 100644 index 00000000..a7d3f60b --- /dev/null +++ b/testintegration/models/models.go @@ -0,0 +1,182 @@ +package models + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "strings" + + "github.com/ditrit/badaas/orm" +) + +type Employee struct { + orm.UUIDModel + + Name string + Boss *Employee // Self-Referential Has One (Employee 0..* -> 0..1 Employee) + BossID *orm.UUID +} + +func (m Employee) Equal(other Employee) bool { + return m.Name == other.Name +} + +type Company struct { + orm.UUIDModel + + Name string + Sellers []Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +type MultiString []string + +func (s *MultiString) Scan(src interface{}) error { + switch typedSrc := src.(type) { + case string: + *s = strings.Split(typedSrc, ",") + return nil + case []byte: + str := string(typedSrc) + *s = strings.Split(str, ",") + return nil + default: + return fmt.Errorf("failed to scan multistring field - source is not a string, is %T", src) + } +} + +func (s MultiString) Value() (driver.Value, error) { + if len(s) == 0 { + return nil, nil + } + return strings.Join(s, ","), nil +} + +func (MultiString) GormDataType() string { + return "text" +} + +type ToBeEmbedded struct { + EmbeddedInt int +} + +type ToBeGormEmbedded struct { + Int int +} + +type Product struct { + orm.UUIDModel + + String string `gorm:"column:string_something_else"` + Int int + IntPointer *int + Float float64 + NullFloat sql.NullFloat64 + Bool bool + NullBool sql.NullBool + ByteArray []byte + MultiString MultiString + ToBeEmbedded + GormEmbedded ToBeGormEmbedded `gorm:"embedded;embeddedPrefix:gorm_embedded_"` +} + +func (m Product) Equal(other Product) bool { + return m.ID == other.ID +} + +type Seller struct { + orm.UUIDModel + + Name string + CompanyID *orm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +type Sale struct { + orm.UUIDModel + + Code int + Description string + + // Sale belongsTo Product (Sale 0..* -> 1 Product) + Product Product + ProductID orm.UUID + + // Sale HasOne Seller (Sale 0..* -> 0..1 Seller) + Seller *Seller + SellerID *orm.UUID +} + +func (m Sale) Equal(other Sale) bool { + return m.ID == other.ID +} + +func (m Seller) Equal(other Seller) bool { + return m.Name == other.Name +} + +type Country struct { + orm.UUIDModel + + Name string + Capital City // Country HasOne City (Country 1 -> 1 City) +} + +type City struct { + orm.UUIDModel + + Name string + CountryID orm.UUID // Country HasOne City (Country 1 -> 1 City) +} + +func (m Country) Equal(other Country) bool { + return m.Name == other.Name +} + +func (m City) Equal(other City) bool { + return m.Name == other.Name +} + +type Person struct { + orm.UUIDModel + + Name string `gorm:"unique;type:VARCHAR(255)"` +} + +func (m Person) TableName() string { + return "persons_and_more_name" +} + +type Bicycle struct { + orm.UUIDModel + + Name string + // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) + Owner Person `gorm:"references:Name;foreignKey:OwnerName"` + OwnerName string +} + +func (m Bicycle) Equal(other Bicycle) bool { + return m.Name == other.Name +} + +type Brand struct { + orm.UIntModel + + Name string +} + +func (m Brand) Equal(other Brand) bool { + return m.Name == other.Name +} + +type Phone struct { + orm.UIntModel + + Name string + // Phone belongsTo Brand (Phone 0..* -> 1 Brand) + Brand Brand + BrandID uint +} + +func (m Phone) Equal(other Phone) bool { + return m.Name == other.Name +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go new file mode 100644 index 00000000..b7d733eb --- /dev/null +++ b/testintegration/operators_test.go @@ -0,0 +1,469 @@ +package testintegration + +import ( + "database/sql" + + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type OperatorsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService orm.CRUDService[models.Product, orm.UUID] +} + +func NewOperatorsIntTestSuite( + db *gorm.DB, + crudProductService orm.CRUDService[models.Product, orm.UUID], +) *OperatorsIntTestSuite { + return &OperatorsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + } +} + +func (ts *OperatorsIntTestSuite) TestEqPointers() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + + intNotMatch := 2 + ts.createProduct("match", 3, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.Eq(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestEqNullableType() { + match := ts.createProduct("match", 0, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.3} + err := ts.db.Save(match).Error + ts.Nil(err) + + notMatch1 := ts.createProduct("not_match", 3, 0, false, nil) + notMatch1.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.2} + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.Eq(1.3), + ), + ) + + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestNotEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.NotEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestLt() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Lt(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestLtOrEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.LtOrEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestGt() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Gt(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestGtOrEq() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.GtOrEq(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 6, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Between(3, 5), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestNotBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.NotBetween(0, 2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNullPointers() { + match := ts.createProduct("match", 0, 0, false, nil) + int1 := 1 + int2 := 2 + + ts.createProduct("not_match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, &int2) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.IsNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNullNullableTypes() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.IsNull[float64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotNullPointers() { + int1 := 1 + match := ts.createProduct("match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.IsNotNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotNullNullableTypes() { + match := ts.createProduct("match", 0, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.IsNotNull[float64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsTrue() { + match := ts.createProduct("match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + orm.IsTrue(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsFalse() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + orm.IsFalse(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotTrue() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: true} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotTrue(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotFalse() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotFalse(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsUnknown() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch1 := ts.createProduct("match", 0, 0, false, nil) + notMatch1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(notMatch1).Error + ts.Nil(err) + + notMatch2 := ts.createProduct("not_match", 0, 0, false, nil) + notMatch2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch2).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsUnknown(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotUnknown() { + match1 := ts.createProduct("", 0, 0, false, nil) + match1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match1).Error + ts.Nil(err) + + match2 := ts.createProduct("", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(match2).Error + ts.Nil(err) + + ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotUnknown(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsDistinct() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.IsDistinct(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotDistinct() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.IsNotDistinct(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestArrayIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.ArrayIn("s1", "s2", "s3"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestArrayNotIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.ArrayNotIn("ns1", "ns2"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestLike() { + match1 := ts.createProduct("basd", 0, 0, false, nil) + match2 := ts.createProduct("cape", 0, 0, false, nil) + + ts.createProduct("bbsd", 0, 0, false, nil) + ts.createProduct("bbasd", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Like("_a%"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestLikeEscape() { + match1 := ts.createProduct("ba_sd", 0, 0, false, nil) + match2 := ts.createProduct("ca_pe", 0, 0, false, nil) + + ts.createProduct("bb_sd", 0, 0, false, nil) + ts.createProduct("bba_sd", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Like("_a!_%").Escape('!'), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go new file mode 100644 index 00000000..67953f65 --- /dev/null +++ b/testintegration/orm_test.go @@ -0,0 +1,100 @@ +package testintegration + +import ( + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +var tGlobal *testing.T + +const ( + username = "root" + password = "postgres" + host = "localhost" + port = 26257 + sslMode = "disable" + dbName = "badaas_db" +) + +func TestBaDaaSORM(t *testing.T) { + tGlobal = t + + fx.New( + // logger + fx.Provide(NewLoggerConfiguration), + logger.LoggerModule, + + // connect to db + fx.Provide(NewGormDBConnection), + + // activate badaas-orm + fx.Provide(GetModels), + orm.AutoMigrate, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + // create crud services for models + orm.GetCRUDServiceModule[models.Seller](), + orm.GetCRUDServiceModule[models.Product](), + orm.GetCRUDServiceModule[models.Sale](), + orm.GetCRUDServiceModule[models.City](), + orm.GetCRUDServiceModule[models.Country](), + orm.GetCRUDServiceModule[models.Employee](), + orm.GetCRUDServiceModule[models.Bicycle](), + orm.GetCRUDServiceModule[models.Phone](), + orm.GetCRUDServiceModule[models.Brand](), + + // create test suites + fx.Provide(NewCRUDRepositoryIntTestSuite), + fx.Provide(NewWhereConditionsIntTestSuite), + fx.Provide(NewJoinConditionsIntTestSuite), + fx.Provide(NewOperatorsIntTestSuite), + + // run tests + fx.Invoke(runORMTestSuites), + ).Run() +} + +func runORMTestSuites( + tsCRUDRepository *CRUDRepositoryIntTestSuite, + tsWhereConditions *WhereConditionsIntTestSuite, + tsJoinConditions *JoinConditionsIntTestSuite, + tsOperators *OperatorsIntTestSuite, + db *gorm.DB, + shutdowner fx.Shutdowner, +) { + suite.Run(tGlobal, tsCRUDRepository) + suite.Run(tGlobal, tsWhereConditions) + suite.Run(tGlobal, tsJoinConditions) + suite.Run(tGlobal, tsOperators) + + shutdowner.Shutdown() +} + +func NewLoggerConfiguration() configuration.LoggerConfiguration { + viper.Set(configuration.LoggerModeKey, "dev") + return configuration.NewLoggerConfiguration() +} + +func NewGormDBConnection(logger *zap.Logger) (*gorm.DB, error) { + return orm.ConnectToDialector( + logger, + orm.CreateDialector(host, username, password, sslMode, dbName, port), + 10, time.Duration(5)*time.Second, + ) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go new file mode 100644 index 00000000..59888ce0 --- /dev/null +++ b/testintegration/where_conditions_test.go @@ -0,0 +1,569 @@ +package testintegration + +import ( + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type WhereConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService orm.CRUDService[models.Product, orm.UUID] + crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudBrandService orm.CRUDService[models.Brand, uint] +} + +func NewWhereConditionsIntTestSuite( + db *gorm.DB, + crudProductService orm.CRUDService[models.Product, orm.UUID], + crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudBrandService orm.CRUDService[models.Brand, uint], +) *WhereConditionsIntTestSuite { + return &WhereConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + crudSaleService: crudSaleService, + crudBrandService: crudBrandService, + } +} + +// ------------------------- GetByID -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityCreated() { + _, err := ts.crudProductService.GetByID(orm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityMatch() { + ts.createProduct("", 0, 0, false, nil) + + _, err := ts.crudProductService.GetByID(orm.NewUUID()) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsTheEntityIfItIsCreate() { + match := ts.createProduct("", 0, 0, false, nil) + + entity, err := ts.crudProductService.GetByID(match.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), match, entity) +} + +// ------------------------- Query -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsTheOnlyOneIfOneEntityCreated() { + match := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsTheListWhenMultipleCreated() { + match1 := ts.createProduct("", 0, 0, false, nil) + match2 := ts.createProduct("", 0, 0, false, nil) + match3 := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Eq("not_created"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNothingMatch() { + ts.createProduct("something_else", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Eq("not_match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsOneIfOnlyOneMatch() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Eq("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsMultipleIfMultipleMatch() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Eq("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfIntType() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Eq(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfFloatType() { + match := ts.createProduct("match", 0, 1.1, false, nil) + ts.createProduct("not_match", 0, 2.2, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + orm.Eq(1.1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfBoolType() { + match := ts.createProduct("match", 0, 0.0, true, nil) + ts.createProduct("not_match", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + orm.Eq(true), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsOfDifferentTypesWorks() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString(orm.Eq("match")), + conditions.ProductInt(orm.Eq(1)), + conditions.ProductBool(orm.Eq(true)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfID() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductId( + orm.Eq(match.ID), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfCreatedAt() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductCreatedAt(orm.Eq(match.CreatedAt)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestDeletedAtConditionIsAddedAutomatically() { + match := ts.createProduct("", 0, 0.0, false, nil) + deleted := ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(deleted).Error) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfDeletedAt() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(match).Error) + + entities, err := ts.crudProductService.Query( + conditions.ProductDeletedAt(orm.Eq(match.DeletedAt.Time)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + match.EmbeddedInt = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductEmbeddedInt(orm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfGormEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + match.GormEmbedded.Int = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductGormEmbeddedInt(orm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfPointerTypeWithValue() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + intNotMatch := 2 + ts.createProduct("not_match", 2, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer(orm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayWithContent() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.ByteArray = []byte{1, 2} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray(orm.Eq([]byte{1, 2})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayEmpty() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.ByteArray = []byte{} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray(orm.Eq([]byte{})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfCustomType() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.MultiString = models.MultiString{"salut", "hola"} + notMatch1.MultiString = models.MultiString{"salut", "hola", "hello"} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductMultiString(orm.Eq(models.MultiString{"salut", "hola"})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationType() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProductId(orm.Eq(product1.ID)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalWithValue() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(orm.Eq(seller1.ID)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalByNil() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(orm.IsNull[orm.UUID]()), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsOnUIntModel() { + match := ts.createBrand("match") + ts.createBrand("not_match") + + entities, err := ts.crudBrandService.Query( + conditions.BrandName(orm.Eq("match")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Brand{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsAreConnectedByAnd() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 5, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt(orm.GtOrEq(3)), + conditions.ProductInt(orm.LtOrEq(4)), + conditions.ProductString(orm.Eq("match")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNot() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not(conditions.ProductInt(orm.Eq(2))), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNotWithMultipleConditionsAreConnectedByAnd() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not( + conditions.ProductInt(orm.Gt(1)), + conditions.ProductInt(orm.Lt(4)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestOr() { + match1 := ts.createProduct("match", 2, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + match3 := ts.createProduct("match_3", 3, 0, false, nil) + + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Or( + conditions.ProductInt(orm.Eq(2)), + conditions.ProductInt(orm.Eq(3)), + conditions.ProductString(orm.Eq("match_3")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNotOr() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + match3 := ts.createProduct("match", 4, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match_string", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not[models.Product]( + orm.Or( + conditions.ProductInt(orm.Eq(2)), + conditions.ProductString(orm.Eq("not_match_string")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString(orm.Eq("match")), + conditions.ProductInt(orm.Lt(2)), + conditions.ProductBool(orm.NotEq(false)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestUnsafeCondition() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + orm.NewUnsafeCondition[models.Product]("%s.int = ?", []any{1}), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestEmptyConnectionConditionMakesNothing() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + orm.And[models.Product](), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestEmptyContainerConditionReturnsError() { + _, err := ts.crudProductService.Query( + orm.Not[models.Product](), + ) + ts.ErrorIs(err, orm.ErrEmptyConditions) +} diff --git a/configuration/time.go b/utils/time.go similarity index 60% rename from configuration/time.go rename to utils/time.go index f71ef280..b075cbb1 100644 --- a/configuration/time.go +++ b/utils/time.go @@ -1,8 +1,8 @@ -package configuration +package utils import "time" // Convert int (seconds) to [time.Duration] -func intToSecond(numberOfSeconds int) time.Duration { +func IntToSecond(numberOfSeconds int) time.Duration { return time.Duration(numberOfSeconds) * time.Second } diff --git a/configuration/time_test.go b/utils/time_test.go similarity index 82% rename from configuration/time_test.go rename to utils/time_test.go index 56134aab..578290ce 100644 --- a/configuration/time_test.go +++ b/utils/time_test.go @@ -1,4 +1,4 @@ -package configuration +package utils import ( "testing" @@ -10,19 +10,19 @@ import ( func TestIntToSecond(t *testing.T) { assert.Equal( t, - intToSecond(20), + IntToSecond(20), time.Duration(20*time.Second), "the duration should be equals", ) assert.Equal( t, - intToSecond(-5), + IntToSecond(-5), time.Duration(-5*time.Second), "the duration should be equals", ) assert.Equal( t, - intToSecond(3600), + IntToSecond(3600), time.Duration(time.Hour), "the duration should be equals", )