You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-15 00:15:15 +02:00
fixed internal package unittests
This commit is contained in:
@ -41,7 +41,7 @@ func main() {
|
|||||||
Database string `default:"shared" envconfig:"DATABASE"`
|
Database string `default:"shared" envconfig:"DATABASE"`
|
||||||
Driver string `default:"postgres" envconfig:"DRIVER"`
|
Driver string `default:"postgres" envconfig:"DRIVER"`
|
||||||
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
||||||
DisableTLS bool `default:"false" envconfig:"DISABLE_TLS"`
|
DisableTLS bool `default:"true" envconfig:"DISABLE_TLS"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,3 +24,53 @@ To build using the docker file, need to be in the project root directory. `Docke
|
|||||||
```bash
|
```bash
|
||||||
docker build -f cmd/web-api/Dockerfile -t saas-web-api .
|
docker build -f cmd/web-api/Dockerfile -t saas-web-api .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
Documentation is generated using [swag](https://github.com/swaggo/swag)
|
||||||
|
|
||||||
|
Download swag by using:
|
||||||
|
```bash
|
||||||
|
go get -u github.com/swaggo/swag/cmd/swag
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `swag init` in the service's root folder which contains the main.go file. This will parse your comments and generate the required files (docs folder and docs/docs.go).
|
||||||
|
```bash
|
||||||
|
swag init
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Trouble shooting
|
||||||
|
|
||||||
|
If you run into errors running `swag init` try the following:
|
||||||
|
|
||||||
|
|
||||||
|
#### cannot find package
|
||||||
|
Try to install the packages to your $GOPATH.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GO111MODULE=off go get github.com/leodido/go-urn
|
||||||
|
GO111MODULE=off go get github.com/lib/pq/oid
|
||||||
|
GO111MODULE=off go get github.com/lib/pq/scram
|
||||||
|
GO111MODULE=off go get github.com/tinylib/msgp/msgp
|
||||||
|
GO111MODULE=off go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace
|
||||||
|
```
|
||||||
|
|
||||||
|
#### error writing go.mod
|
||||||
|
|
||||||
|
Need to update pkg directory permissions.
|
||||||
|
|
||||||
|
Full error:
|
||||||
|
```bash
|
||||||
|
error writing go.mod: open /Users/leebrown/go/pkg/mod/github.com/lib/pq@v1.1.1/go.mod691440060.tmp: permission denied
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure the `pkg` directory used for go module cache has the correct permissions.
|
||||||
|
```bash
|
||||||
|
sudo chown -R $(whoami):staff ${HOME}/go/pkg
|
||||||
|
sudo chmod -R 755 ${HOME}/go/pkg
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
// This file was generated by swaggo/swag at
|
// This file was generated by swaggo/swag at
|
||||||
// 2019-06-24 15:42:25.999684 -0800 AKDT m=+0.030714022
|
// 2019-06-24 20:15:37.524606 -0800 AKDT m=+13.872100491
|
||||||
|
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
@ -16,10 +16,10 @@ var doc = `{
|
|||||||
"info": {
|
"info": {
|
||||||
"description": "This is a sample server celler server.",
|
"description": "This is a sample server celler server.",
|
||||||
"title": "SaaS Example API",
|
"title": "SaaS Example API",
|
||||||
"termsOfService": "http://geeksinthewoods.com/terms",
|
"termsOfService": "/terms",
|
||||||
"contact": {
|
"contact": {
|
||||||
"name": "API Support",
|
"name": "API Support",
|
||||||
"url": "https://gitlab.com/geeks-accelerator/oss/saas-starter-kit",
|
"url": "/support",
|
||||||
"email": "support@geeksinthewoods.com"
|
"email": "support@geeksinthewoods.com"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
@ -30,20 +30,140 @@ var doc = `{
|
|||||||
},
|
},
|
||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {},
|
"paths": {
|
||||||
|
"/accounts/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "get string by ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Read returns the specified account from the system.",
|
||||||
|
"operationId": "get-string-by-int",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/account.Account"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"Token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "qwerty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"account.Account": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address1": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"address2": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"archived_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"billing_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"signup_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "AccountStatus"
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"zipcode": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"web.Error": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"err": {
|
||||||
|
"type": "error"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "FieldError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
"ApiKeyAuth": {
|
|
||||||
"type": "apiKey",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
"BasicAuth": {
|
|
||||||
"type": "basic"
|
|
||||||
},
|
|
||||||
"OAuth2Password": {
|
"OAuth2Password": {
|
||||||
"type": "oauth2",
|
"type": "oauth2",
|
||||||
"flow": "password",
|
"flow": "password",
|
||||||
"tokenUrl": "https://example.com/v1/oauth/token"
|
"tokenUrl": "/v1/oauth/token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"description": "This is a sample server celler server.",
|
"description": "This is a sample server celler server.",
|
||||||
"title": "SaaS Example API",
|
"title": "SaaS Example API",
|
||||||
"termsOfService": "http://geeksinthewoods.com/terms",
|
"termsOfService": "/terms",
|
||||||
"contact": {
|
"contact": {
|
||||||
"name": "API Support",
|
"name": "API Support",
|
||||||
"url": "https://gitlab.com/geeks-accelerator/oss/saas-starter-kit",
|
"url": "/support",
|
||||||
"email": "support@geeksinthewoods.com"
|
"email": "support@geeksinthewoods.com"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
@ -17,20 +17,140 @@
|
|||||||
},
|
},
|
||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {},
|
"paths": {
|
||||||
|
"/accounts/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "get string by ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Read returns the specified account from the system.",
|
||||||
|
"operationId": "get-string-by-int",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/account.Account"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"Token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "qwerty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/web.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"account.Account": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address1": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"address2": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"archived_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"billing_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"signup_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "AccountStatus"
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"zipcode": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"web.Error": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"err": {
|
||||||
|
"type": "error"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "FieldError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
"ApiKeyAuth": {
|
|
||||||
"type": "apiKey",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
"BasicAuth": {
|
|
||||||
"type": "basic"
|
|
||||||
},
|
|
||||||
"OAuth2Password": {
|
"OAuth2Password": {
|
||||||
"type": "oauth2",
|
"type": "oauth2",
|
||||||
"flow": "password",
|
"flow": "password",
|
||||||
"tokenUrl": "https://example.com/v1/oauth/token"
|
"tokenUrl": "/v1/oauth/token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +1,106 @@
|
|||||||
basePath: '{{.BasePath}}'
|
basePath: '{{.BasePath}}'
|
||||||
|
definitions:
|
||||||
|
account.Account:
|
||||||
|
properties:
|
||||||
|
address1:
|
||||||
|
type: string
|
||||||
|
address2:
|
||||||
|
type: string
|
||||||
|
archived_at:
|
||||||
|
type: string
|
||||||
|
billing_user_id:
|
||||||
|
type: string
|
||||||
|
city:
|
||||||
|
type: string
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
signup_user_id:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: AccountStatus
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
zipcode:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
web.Error:
|
||||||
|
properties:
|
||||||
|
err:
|
||||||
|
type: error
|
||||||
|
fields:
|
||||||
|
items:
|
||||||
|
type: FieldError
|
||||||
|
type: array
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
host: '{{.Host}}'
|
host: '{{.Host}}'
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: support@geeksinthewoods.com
|
email: support@geeksinthewoods.com
|
||||||
name: API Support
|
name: API Support
|
||||||
url: https://gitlab.com/geeks-accelerator/oss/saas-starter-kit
|
url: /support
|
||||||
description: This is a sample server celler server.
|
description: This is a sample server celler server.
|
||||||
license:
|
license:
|
||||||
name: Apache 2.0
|
name: Apache 2.0
|
||||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
termsOfService: http://geeksinthewoods.com/terms
|
termsOfService: /terms
|
||||||
title: SaaS Example API
|
title: SaaS Example API
|
||||||
version: '{{.Version}}'
|
version: '{{.Version}}'
|
||||||
paths: {}
|
paths:
|
||||||
|
/accounts/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: get string by ID
|
||||||
|
operationId: get-string-by-int
|
||||||
|
parameters:
|
||||||
|
- description: Account ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
headers:
|
||||||
|
Token:
|
||||||
|
description: qwerty
|
||||||
|
type: string
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/account.Account'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/web.Error'
|
||||||
|
type: object
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/web.Error'
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/web.Error'
|
||||||
|
type: object
|
||||||
|
summary: Read returns the specified account from the system.
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
ApiKeyAuth:
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
type: apiKey
|
|
||||||
BasicAuth:
|
|
||||||
type: basic
|
|
||||||
OAuth2Password:
|
OAuth2Password:
|
||||||
flow: password
|
flow: password
|
||||||
tokenUrl: https://example.com/v1/oauth/token
|
tokenUrl: /v1/oauth/token
|
||||||
type: oauth2
|
type: oauth2
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
@ -39,7 +39,19 @@ func (a *Account) Find(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return web.RespondJson(ctx, w, res, http.StatusOK)
|
return web.RespondJson(ctx, w, res, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read returns the specified account from the system.
|
// Read godoc
|
||||||
|
// @Summary Read returns the specified account from the system.
|
||||||
|
// @Description get string by ID
|
||||||
|
// @ID get-string-by-int
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Account ID"
|
||||||
|
// @Success 200 {object} account.Account
|
||||||
|
// @Header 200 {string} Token "qwerty"
|
||||||
|
// @Failure 400 {object} web.Error
|
||||||
|
// @Failure 403 {object} web.Error
|
||||||
|
// @Failure 404 {object} web.Error
|
||||||
|
// @Router /accounts/{id} [get]
|
||||||
func (a *Account) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
func (a *Account) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
claims, ok := ctx.Value(auth.Key).(auth.Claims)
|
claims, ok := ctx.Value(auth.Key).(auth.Claims)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -40,7 +40,7 @@ func API(shutdown chan os.Signal, log *log.Logger, masterDB *sqlx.DB, redis *red
|
|||||||
app.Handle("PATCH", "/v1/users/switch-account/:accountId", u.SwitchAccount, mid.Authenticate(authenticator))
|
app.Handle("PATCH", "/v1/users/switch-account/:accountId", u.SwitchAccount, mid.Authenticate(authenticator))
|
||||||
|
|
||||||
// This route is not authenticated
|
// This route is not authenticated
|
||||||
app.Handle("GET", "/v1/oauth/token", u.Token)
|
app.Handle("POST", "/v1/oauth/token", u.Token)
|
||||||
|
|
||||||
// Register account endpoints.
|
// Register account endpoints.
|
||||||
a := Account{
|
a := Account{
|
||||||
@ -65,8 +65,10 @@ func API(shutdown chan os.Signal, log *log.Logger, masterDB *sqlx.DB, redis *red
|
|||||||
app.Handle("DELETE", "/v1/projects/:id", p.Delete, mid.Authenticate(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("DELETE", "/v1/projects/:id", p.Delete, mid.Authenticate(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
|
||||||
// Register swagger documentation.
|
// Register swagger documentation.
|
||||||
app.Handle("GET", "/swagger/", saasSwagger.WrapHandler, mid.Authenticate(authenticator))
|
// TODO: Add authentication. Current authenticator requires an Authorization header
|
||||||
app.Handle("GET", "/swagger/*", saasSwagger.WrapHandler, mid.Authenticate(authenticator))
|
// which breaks the browser experience.
|
||||||
|
app.Handle("GET", "/swagger/", saasSwagger.WrapHandler)
|
||||||
|
app.Handle("GET", "/swagger/*", saasSwagger.WrapHandler)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
@ -42,23 +42,17 @@ var service = "WEB_API"
|
|||||||
|
|
||||||
// @title SaaS Example API
|
// @title SaaS Example API
|
||||||
// @description This is a sample server celler server.
|
// @description This is a sample server celler server.
|
||||||
// @termsOfService http://geeksinthewoods.com/terms
|
// @termsOfService http://example.com/terms
|
||||||
|
|
||||||
// @contact.name API Support
|
// @contact.name API Support
|
||||||
// @contact.email support@geeksinthewoods.com
|
// @contact.email support@geeksinthewoods.com
|
||||||
// @contact.url https://gitlab.com/geeks-accelerator/oss/saas-starter-kit
|
// @contact.url http://example.com/support
|
||||||
|
|
||||||
// @license.name Apache 2.0
|
// @license.name Apache 2.0
|
||||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
// @securityDefinitions.basic BasicAuth
|
|
||||||
|
|
||||||
// @securityDefinitions.apikey ApiKeyAuth
|
|
||||||
// @in header
|
|
||||||
// @name Authorization
|
|
||||||
|
|
||||||
// @securitydefinitions.oauth2.password OAuth2Password
|
// @securitydefinitions.oauth2.password OAuth2Password
|
||||||
// @tokenUrl https://example.com/v1/oauth/token
|
// @tokenUrl /v1/oauth/token
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
@ -101,7 +95,7 @@ func main() {
|
|||||||
Database string `default:"shared" envconfig:"DATABASE"`
|
Database string `default:"shared" envconfig:"DATABASE"`
|
||||||
Driver string `default:"postgres" envconfig:"DRIVER"`
|
Driver string `default:"postgres" envconfig:"DRIVER"`
|
||||||
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
||||||
DisableTLS bool `default:"false" envconfig:"DISABLE_TLS"`
|
DisableTLS bool `default:"true" envconfig:"DISABLE_TLS"`
|
||||||
}
|
}
|
||||||
Trace struct {
|
Trace struct {
|
||||||
Host string `default:"127.0.0.1" envconfig:"DD_TRACE_AGENT_HOSTNAME"`
|
Host string `default:"127.0.0.1" envconfig:"DD_TRACE_AGENT_HOSTNAME"`
|
||||||
|
4
example-project/cmd/web-api/sample.env
Normal file
4
example-project/cmd/web-api/sample.env
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export WEB_API_DB_HOST=127.0.0.1:5433
|
||||||
|
export WEB_API_DB_USER=postgres
|
||||||
|
export WEB_API_DB_PASS=postgres
|
||||||
|
export WEB_API_DB_DISABLE_TLS=true
|
@ -94,7 +94,7 @@ func main() {
|
|||||||
Database string `default:"shared" envconfig:"DATABASE"`
|
Database string `default:"shared" envconfig:"DATABASE"`
|
||||||
Driver string `default:"postgres" envconfig:"DRIVER"`
|
Driver string `default:"postgres" envconfig:"DRIVER"`
|
||||||
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
||||||
DisableTLS bool `default:"false" envconfig:"DISABLE_TLS"`
|
DisableTLS bool `default:"true" envconfig:"DISABLE_TLS"`
|
||||||
}
|
}
|
||||||
Trace struct {
|
Trace struct {
|
||||||
Host string `default:"127.0.0.1" envconfig:"DD_TRACE_AGENT_HOSTNAME"`
|
Host string `default:"127.0.0.1" envconfig:"DD_TRACE_AGENT_HOSTNAME"`
|
||||||
|
4
example-project/cmd/web-app/sample.env
Normal file
4
example-project/cmd/web-app/sample.env
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export WEB_APP_DB_HOST=127.0.0.1:5433
|
||||||
|
export WEB_APP_DB_USER=postgres
|
||||||
|
export WEB_APP_DB_PASS=postgres
|
||||||
|
export WEB_APP_DB_DISABLE_TLS=true
|
@ -13,14 +13,14 @@ require (
|
|||||||
github.com/go-playground/locales v0.12.1
|
github.com/go-playground/locales v0.12.1
|
||||||
github.com/go-playground/universal-translator v0.16.0
|
github.com/go-playground/universal-translator v0.16.0
|
||||||
github.com/go-redis/redis v6.15.2+incompatible
|
github.com/go-redis/redis v6.15.2+incompatible
|
||||||
github.com/google/go-cmp v0.2.0
|
github.com/google/go-cmp v0.3.0
|
||||||
github.com/gorilla/schema v1.1.0
|
github.com/gorilla/schema v1.1.0
|
||||||
github.com/huandu/go-sqlbuilder v1.4.0
|
github.com/huandu/go-sqlbuilder v1.4.0
|
||||||
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365
|
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/kelseyhightower/envconfig v1.3.0
|
github.com/kelseyhightower/envconfig v1.3.0
|
||||||
github.com/leodido/go-urn v1.1.0 // indirect
|
github.com/leodido/go-urn v1.1.0
|
||||||
github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01
|
github.com/lib/pq v1.1.1
|
||||||
github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 // indirect
|
github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||||
@ -39,9 +39,9 @@ require (
|
|||||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 // indirect
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190624190245-7f2218787638 // indirect
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 // indirect
|
||||||
google.golang.org/appengine v1.6.0 // indirect
|
google.golang.org/appengine v1.6.0 // indirect
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.14.0
|
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.0
|
gopkg.in/go-playground/validator.v9 v9.29.0
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
|
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
|
||||||
|
@ -53,6 +53,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
||||||
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
@ -79,6 +81,8 @@ github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
|||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||||
|
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01 h1:EPw7R3OAyxHBCyl0oqh3lUZqS5lu3KSxzzGasE0opXQ=
|
github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01 h1:EPw7R3OAyxHBCyl0oqh3lUZqS5lu3KSxzzGasE0opXQ=
|
||||||
github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
@ -153,11 +157,15 @@ golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190624190245-7f2218787638 h1:uIfBkD8gLczr4XDgYpt/qJYds2YJwZRNw4zs7wSnNhk=
|
golang.org/x/tools v0.0.0-20190624190245-7f2218787638 h1:uIfBkD8gLczr4XDgYpt/qJYds2YJwZRNw4zs7wSnNhk=
|
||||||
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 h1:1mMox4TgefDwqluYCv677yNXwlfTkija4owZve/jr78=
|
||||||
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
|
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
|
||||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.14.0 h1:p/8j8WV6HC+6c99FMWIPrPPs+PiXU/ShrBxHbO8S8V0=
|
gopkg.in/DataDog/dd-trace-go.v1 v1.14.0 h1:p/8j8WV6HC+6c99FMWIPrPPs+PiXU/ShrBxHbO8S8V0=
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.14.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
|
gopkg.in/DataDog/dd-trace-go.v1 v1.14.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
|
||||||
|
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0 h1:2LhklnAJsRSelbnBrrE5QuRleRDkmOh2JWxOtIX6yec=
|
||||||
|
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -232,7 +232,7 @@ func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validation an name is unique excluding the current account ID.
|
// Validation an name is unique excluding the current account ID.
|
||||||
func uniqueName(ctx context.Context, dbConn *sqlx.DB, name, accountId string) (bool, error) {
|
func UniqueName(ctx context.Context, dbConn *sqlx.DB, name, accountId string) (bool, error) {
|
||||||
query := sqlbuilder.NewSelectBuilder().Select("id").From(accountTableName)
|
query := sqlbuilder.NewSelectBuilder().Select("id").From(accountTableName)
|
||||||
query.Where(query.And(
|
query.Where(query.And(
|
||||||
query.Equal("name", name),
|
query.Equal("name", name),
|
||||||
@ -264,7 +264,7 @@ func Create(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req Accoun
|
|||||||
v := validator.New()
|
v := validator.New()
|
||||||
|
|
||||||
// Validation email address is unique in the database.
|
// Validation email address is unique in the database.
|
||||||
uniq, err := uniqueName(ctx, dbConn, req.Name, "")
|
uniq, err := UniqueName(ctx, dbConn, req.Name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -372,7 +372,7 @@ func Update(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req Accoun
|
|||||||
|
|
||||||
// Validation name is unique in the database.
|
// Validation name is unique in the database.
|
||||||
if req.Name != nil {
|
if req.Name != nil {
|
||||||
uniq, err := uniqueName(ctx, dbConn, *req.Name, req.ID)
|
uniq, err := UniqueName(ctx, dbConn, *req.Name, req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -583,22 +583,14 @@ func Delete(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, accountID
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the delete SQL statement.
|
// Start a new transaction to handle rollbacks on error.
|
||||||
query := sqlbuilder.NewDeleteBuilder()
|
tx, err := dbConn.Begin()
|
||||||
query.DeleteFrom(accountTableName)
|
|
||||||
query.Where(query.Equal("id", req.ID))
|
|
||||||
|
|
||||||
// Execute the query with the provided context.
|
|
||||||
sql, args := query.Build()
|
|
||||||
sql = dbConn.Rebind(sql)
|
|
||||||
_, err = dbConn.ExecContext(ctx, sql, args...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrapf(err, "query - %s", query.String())
|
return errors.WithStack(err)
|
||||||
err = errors.WithMessagef(err, "delete account %s failed", req.ID)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all the associated user accounts
|
// Delete all the associated user accounts.
|
||||||
|
// Required to execute first to avoid foreign key constraints.
|
||||||
{
|
{
|
||||||
// Build the delete SQL statement.
|
// Build the delete SQL statement.
|
||||||
query := sqlbuilder.NewDeleteBuilder()
|
query := sqlbuilder.NewDeleteBuilder()
|
||||||
@ -610,13 +602,54 @@ func Delete(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, accountID
|
|||||||
// Execute the query with the provided context.
|
// Execute the query with the provided context.
|
||||||
sql, args := query.Build()
|
sql, args := query.Build()
|
||||||
sql = dbConn.Rebind(sql)
|
sql = dbConn.Rebind(sql)
|
||||||
_, err = dbConn.ExecContext(ctx, sql, args...)
|
_, err = tx.ExecContext(ctx, sql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
|
||||||
err = errors.Wrapf(err, "query - %s", query.String())
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
err = errors.WithMessagef(err, "delete users for account %s failed", req.ID)
|
err = errors.WithMessagef(err, "delete users for account %s failed", req.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the delete SQL statement.
|
||||||
|
query := sqlbuilder.NewDeleteBuilder()
|
||||||
|
query.DeleteFrom(accountTableName)
|
||||||
|
query.Where(query.Equal("id", req.ID))
|
||||||
|
|
||||||
|
// Execute the query with the provided context.
|
||||||
|
sql, args := query.Build()
|
||||||
|
sql = dbConn.Rebind(sql)
|
||||||
|
_, err = tx.ExecContext(ctx, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
err = errors.WithMessagef(err, "delete account %s failed", req.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockAccount returns a fake Account for testing.
|
||||||
|
func MockAccount(ctx context.Context, dbConn *sqlx.DB, now time.Time) (*Account, error) {
|
||||||
|
s := AccountStatus_Active
|
||||||
|
|
||||||
|
req := AccountCreateRequest{
|
||||||
|
Name: uuid.NewRandom().String(),
|
||||||
|
Address1: "103 East Main St",
|
||||||
|
Address2: "Unit 546",
|
||||||
|
City: "Valdez",
|
||||||
|
Region: "AK",
|
||||||
|
Country: "USA",
|
||||||
|
Zipcode: "99686",
|
||||||
|
Status: &s,
|
||||||
|
}
|
||||||
|
return Create(ctx, auth.Claims{}, dbConn, req, now)
|
||||||
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lib/pq"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
@ -142,25 +142,25 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
|
|
||||||
var accountTests = []struct {
|
var accountTests = []struct {
|
||||||
name string
|
name string
|
||||||
req CreateAccountRequest
|
req AccountCreateRequest
|
||||||
expected func(req CreateAccountRequest, res *Account) *Account
|
expected func(req AccountCreateRequest, res *Account) *Account
|
||||||
error error
|
error error
|
||||||
}{
|
}{
|
||||||
{"Required Fields",
|
{"Required Fields",
|
||||||
CreateAccountRequest{},
|
AccountCreateRequest{},
|
||||||
func(req CreateAccountRequest, res *Account) *Account {
|
func(req AccountCreateRequest, res *Account) *Account {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
errors.New("Key: 'CreateAccountRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
errors.New("Key: 'AccountCreateRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateAccountRequest.Address1' Error:Field validation for 'Address1' failed on the 'required' tag\n" +
|
"Key: 'AccountCreateRequest.Address1' Error:Field validation for 'Address1' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateAccountRequest.City' Error:Field validation for 'City' failed on the 'required' tag\n" +
|
"Key: 'AccountCreateRequest.City' Error:Field validation for 'City' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateAccountRequest.Region' Error:Field validation for 'Region' failed on the 'required' tag\n" +
|
"Key: 'AccountCreateRequest.Region' Error:Field validation for 'Region' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateAccountRequest.Country' Error:Field validation for 'Country' failed on the 'required' tag\n" +
|
"Key: 'AccountCreateRequest.Country' Error:Field validation for 'Country' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateAccountRequest.Zipcode' Error:Field validation for 'Zipcode' failed on the 'required' tag"),
|
"Key: 'AccountCreateRequest.Zipcode' Error:Field validation for 'Zipcode' failed on the 'required' tag"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{"Default Timezone & Status",
|
{"Default Timezone & Status",
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -169,7 +169,7 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(req CreateAccountRequest, res *Account) *Account {
|
func(req AccountCreateRequest, res *Account) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Address1: req.Address1,
|
Address1: req.Address1,
|
||||||
@ -191,7 +191,7 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{"Valid Status",
|
{"Valid Status",
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -201,10 +201,10 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
Status: &invalidStatus,
|
Status: &invalidStatus,
|
||||||
},
|
},
|
||||||
func(req CreateAccountRequest, res *Account) *Account {
|
func(req AccountCreateRequest, res *Account) *Account {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
errors.New("Key: 'CreateAccountRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag"),
|
errors.New("Key: 'AccountCreateRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ func TestCreateValidationNameUnique(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
req1 := CreateAccountRequest{
|
req1 := AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -277,7 +277,7 @@ func TestCreateValidationNameUnique(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
req2 := CreateAccountRequest{
|
req2 := AccountCreateRequest{
|
||||||
Name: account1.Name,
|
Name: account1.Name,
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -286,7 +286,7 @@ func TestCreateValidationNameUnique(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("Key: 'CreateAccountRequest.Name' Error:Field validation for 'Name' failed on the 'unique' tag")
|
expectedErr := errors.New("Key: 'AccountCreateRequest.Name' Error:Field validation for 'Name' failed on the 'unique' tag")
|
||||||
_, err = Create(ctx, auth.Claims{}, test.MasterDB, req2, now)
|
_, err = Create(ctx, auth.Claims{}, test.MasterDB, req2, now)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("\t\tWant: %+v", expectedErr)
|
t.Logf("\t\tWant: %+v", expectedErr)
|
||||||
@ -310,13 +310,13 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
var accountTests = []struct {
|
var accountTests = []struct {
|
||||||
name string
|
name string
|
||||||
claims auth.Claims
|
claims auth.Claims
|
||||||
req CreateAccountRequest
|
req AccountCreateRequest
|
||||||
error error
|
error error
|
||||||
}{
|
}{
|
||||||
// Internal request, should bypass ACL.
|
// Internal request, should bypass ACL.
|
||||||
{"EmptyClaims",
|
{"EmptyClaims",
|
||||||
auth.Claims{},
|
auth.Claims{},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -336,7 +336,7 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
Audience: "acc1",
|
Audience: "acc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -356,7 +356,7 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
Audience: "acc1",
|
Audience: "acc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -396,24 +396,24 @@ func TestUpdateValidation(t *testing.T) {
|
|||||||
// TODO: actually create the account so can test the output of findbyId
|
// TODO: actually create the account so can test the output of findbyId
|
||||||
type accountTest struct {
|
type accountTest struct {
|
||||||
name string
|
name string
|
||||||
req UpdateAccountRequest
|
req AccountUpdateRequest
|
||||||
error error
|
error error
|
||||||
}
|
}
|
||||||
|
|
||||||
var accountTests = []accountTest{
|
var accountTests = []accountTest{
|
||||||
{"Required Fields",
|
{"Required Fields",
|
||||||
UpdateAccountRequest{},
|
AccountUpdateRequest{},
|
||||||
errors.New("Key: 'UpdateAccountRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag"),
|
errors.New("Key: 'AccountUpdateRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidStatus := AccountStatus("xxxxxx")
|
invalidStatus := AccountStatus("xxxxxx")
|
||||||
accountTests = append(accountTests, accountTest{"Valid Status",
|
accountTests = append(accountTests, accountTest{"Valid Status",
|
||||||
UpdateAccountRequest{
|
AccountUpdateRequest{
|
||||||
ID: uuid.NewRandom().String(),
|
ID: uuid.NewRandom().String(),
|
||||||
Status: &invalidStatus,
|
Status: &invalidStatus,
|
||||||
},
|
},
|
||||||
errors.New("Key: 'UpdateAccountRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag"),
|
errors.New("Key: 'AccountUpdateRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag"),
|
||||||
})
|
})
|
||||||
|
|
||||||
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
||||||
@ -459,7 +459,7 @@ func TestUpdateValidationNameUnique(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
req1 := CreateAccountRequest{
|
req1 := AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -474,7 +474,7 @@ func TestUpdateValidationNameUnique(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
req2 := CreateAccountRequest{
|
req2 := AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -490,11 +490,11 @@ func TestUpdateValidationNameUnique(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to set the email for account 1 on account 2
|
// Try to set the email for account 1 on account 2
|
||||||
updateReq := UpdateAccountRequest{
|
updateReq := AccountUpdateRequest{
|
||||||
ID: account2.ID,
|
ID: account2.ID,
|
||||||
Name: &account1.Name,
|
Name: &account1.Name,
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("Key: 'UpdateAccountRequest.Name' Error:Field validation for 'Name' failed on the 'unique' tag")
|
expectedErr := errors.New("Key: 'AccountUpdateRequest.Name' Error:Field validation for 'Name' failed on the 'unique' tag")
|
||||||
err = Update(ctx, auth.Claims{}, test.MasterDB, updateReq, now)
|
err = Update(ctx, auth.Claims{}, test.MasterDB, updateReq, now)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("\t\tWant: %+v", expectedErr)
|
t.Logf("\t\tWant: %+v", expectedErr)
|
||||||
@ -518,10 +518,10 @@ func TestCrud(t *testing.T) {
|
|||||||
type accountTest struct {
|
type accountTest struct {
|
||||||
name string
|
name string
|
||||||
claims func(*Account, string) auth.Claims
|
claims func(*Account, string) auth.Claims
|
||||||
create CreateAccountRequest
|
create AccountCreateRequest
|
||||||
update func(*Account) UpdateAccountRequest
|
update func(*Account) AccountUpdateRequest
|
||||||
updateErr error
|
updateErr error
|
||||||
expected func(*Account, UpdateAccountRequest) *Account
|
expected func(*Account, AccountUpdateRequest) *Account
|
||||||
findErr error
|
findErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +532,7 @@ func TestCrud(t *testing.T) {
|
|||||||
func(account *Account, userId string) auth.Claims {
|
func(account *Account, userId string) auth.Claims {
|
||||||
return auth.Claims{}
|
return auth.Claims{}
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -541,15 +541,15 @@ func TestCrud(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(account *Account) UpdateAccountRequest {
|
func(account *Account) AccountUpdateRequest {
|
||||||
name := uuid.NewRandom().String()
|
name := uuid.NewRandom().String()
|
||||||
return UpdateAccountRequest{
|
return AccountUpdateRequest{
|
||||||
ID: account.ID,
|
ID: account.ID,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(account *Account, req UpdateAccountRequest) *Account {
|
func(account *Account, req AccountUpdateRequest) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: *req.Name,
|
Name: *req.Name,
|
||||||
// Copy this fields from the created account.
|
// Copy this fields from the created account.
|
||||||
@ -583,7 +583,7 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -592,15 +592,15 @@ func TestCrud(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(account *Account) UpdateAccountRequest {
|
func(account *Account) AccountUpdateRequest {
|
||||||
name := uuid.NewRandom().String()
|
name := uuid.NewRandom().String()
|
||||||
return UpdateAccountRequest{
|
return AccountUpdateRequest{
|
||||||
ID: account.ID,
|
ID: account.ID,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ErrForbidden,
|
ErrForbidden,
|
||||||
func(account *Account, req UpdateAccountRequest) *Account {
|
func(account *Account, req AccountUpdateRequest) *Account {
|
||||||
return account
|
return account
|
||||||
},
|
},
|
||||||
ErrNotFound,
|
ErrNotFound,
|
||||||
@ -617,7 +617,7 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -626,15 +626,15 @@ func TestCrud(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(account *Account) UpdateAccountRequest {
|
func(account *Account) AccountUpdateRequest {
|
||||||
name := uuid.NewRandom().String()
|
name := uuid.NewRandom().String()
|
||||||
return UpdateAccountRequest{
|
return AccountUpdateRequest{
|
||||||
ID: account.ID,
|
ID: account.ID,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(account *Account, req UpdateAccountRequest) *Account {
|
func(account *Account, req AccountUpdateRequest) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: *req.Name,
|
Name: *req.Name,
|
||||||
// Copy this fields from the created account.
|
// Copy this fields from the created account.
|
||||||
@ -668,7 +668,7 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -677,15 +677,15 @@ func TestCrud(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(account *Account) UpdateAccountRequest {
|
func(account *Account) AccountUpdateRequest {
|
||||||
name := uuid.NewRandom().String()
|
name := uuid.NewRandom().String()
|
||||||
return UpdateAccountRequest{
|
return AccountUpdateRequest{
|
||||||
ID: account.ID,
|
ID: account.ID,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ErrForbidden,
|
ErrForbidden,
|
||||||
func(account *Account, req UpdateAccountRequest) *Account {
|
func(account *Account, req AccountUpdateRequest) *Account {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
ErrNotFound,
|
ErrNotFound,
|
||||||
@ -702,7 +702,7 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateAccountRequest{
|
AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -711,15 +711,15 @@ func TestCrud(t *testing.T) {
|
|||||||
Country: "USA",
|
Country: "USA",
|
||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
func(account *Account) UpdateAccountRequest {
|
func(account *Account) AccountUpdateRequest {
|
||||||
name := uuid.NewRandom().String()
|
name := uuid.NewRandom().String()
|
||||||
return UpdateAccountRequest{
|
return AccountUpdateRequest{
|
||||||
ID: account.ID,
|
ID: account.ID,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(account *Account, req UpdateAccountRequest) *Account {
|
func(account *Account, req AccountUpdateRequest) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: *req.Name,
|
Name: *req.Name,
|
||||||
// Copy this fields from the created account.
|
// Copy this fields from the created account.
|
||||||
@ -846,7 +846,7 @@ func TestFind(t *testing.T) {
|
|||||||
|
|
||||||
var accounts []*Account
|
var accounts []*Account
|
||||||
for i := 0; i <= 4; i++ {
|
for i := 0; i <= 4; i++ {
|
||||||
account, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, CreateAccountRequest{
|
account, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, AccountCreateRequest{
|
||||||
Name: uuid.NewRandom().String(),
|
Name: uuid.NewRandom().String(),
|
||||||
Address1: "103 East Main St",
|
Address1: "103 East Main St",
|
||||||
Address2: "Unit 546",
|
Address2: "Unit 546",
|
||||||
@ -990,12 +990,37 @@ func mockUserAccount(accountId, userId string, now time.Time, roles ...string) e
|
|||||||
roleArr = append(roleArr, r)
|
roleArr = append(roleArr, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := mockUser(userId, now)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Build the insert SQL statement.
|
// Build the insert SQL statement.
|
||||||
query := sqlbuilder.NewInsertBuilder()
|
query := sqlbuilder.NewInsertBuilder()
|
||||||
query.InsertInto(userAccountTableName)
|
query.InsertInto(userAccountTableName)
|
||||||
query.Cols("id", "user_id", "account_id", "roles", "created_at", "updated_at")
|
query.Cols("id", "user_id", "account_id", "roles", "created_at", "updated_at")
|
||||||
query.Values(uuid.NewRandom().String(), userId, accountId, roleArr, now, now)
|
query.Values(uuid.NewRandom().String(), userId, accountId, roleArr, now, now)
|
||||||
|
|
||||||
|
// Execute the query with the provided context.
|
||||||
|
sql, args := query.Build()
|
||||||
|
sql = test.MasterDB.Rebind(sql)
|
||||||
|
_, err = test.MasterDB.ExecContext(tests.Context(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockUser(userId string, now time.Time) error {
|
||||||
|
|
||||||
|
// Build the insert SQL statement.
|
||||||
|
query := sqlbuilder.NewInsertBuilder()
|
||||||
|
query.InsertInto("users")
|
||||||
|
query.Cols("id", "email", "password_hash", "password_salt", "created_at", "updated_at")
|
||||||
|
query.Values(userId, uuid.NewRandom().String(), "-", "-", now, now)
|
||||||
|
|
||||||
// Execute the query with the provided context.
|
// Execute the query with the provided context.
|
||||||
sql, args := query.Build()
|
sql, args := query.Build()
|
||||||
sql = test.MasterDB.Rebind(sql)
|
sql = test.MasterDB.Rebind(sql)
|
||||||
|
@ -92,7 +92,7 @@ func SaasWrapHandler(confs ...func(c *Config)) web.Handler {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return web.NewRequestError(err, http.StatusInternalServerError)
|
return web.NewRequestError(err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
return web.RespondJson(ctx, w, doc, http.StatusOK)
|
return web.RespondJson(ctx, w, []byte(doc), http.StatusOK)
|
||||||
default:
|
default:
|
||||||
if strings.HasSuffix(path, ".html") {
|
if strings.HasSuffix(path, ".html") {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
@ -97,7 +97,7 @@ func (a *Authenticator) GenerateToken(claims Claims) (string, error) {
|
|||||||
tkn := jwt.NewWithClaims(method, claims)
|
tkn := jwt.NewWithClaims(method, claims)
|
||||||
tkn.Header["kid"] = a.keyID
|
tkn.Header["kid"] = a.keyID
|
||||||
|
|
||||||
str, err := tkn.SignedString(a.privateKey)
|
str, err := tkn.SignedString(a.privateKey.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "signing token")
|
return "", errors.Wrap(err, "signing token")
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,77 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testMain(m *testing.M) int {
|
func testMain(m *testing.M) int {
|
||||||
|
tests.DisableDb = true
|
||||||
|
|
||||||
test = tests.New()
|
test = tests.New()
|
||||||
defer test.TearDown()
|
defer test.TearDown()
|
||||||
|
|
||||||
return m.Run()
|
return m.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticator(t *testing.T) {
|
// TestAuthenticatorFile validates File storage.
|
||||||
|
func TestAuthenticatorFile(t *testing.T) {
|
||||||
|
|
||||||
|
var authTests = []struct {
|
||||||
|
name string
|
||||||
|
now time.Time
|
||||||
|
keyExpiration time.Duration
|
||||||
|
error error
|
||||||
|
}{
|
||||||
|
{"NoKeyExpiration", time.Now(), time.Duration(0), nil},
|
||||||
|
{"KeyExpirationOk", time.Now(), time.Duration(time.Second * 3600), nil},
|
||||||
|
{"KeyExpirationDisabled", time.Now().Add(time.Second * 3600 * 3), time.Duration(time.Second * 3600), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token.
|
||||||
|
signedClaims := auth.Claims{
|
||||||
|
Roles: []string{auth.RoleAdmin},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Given the need to validate initiating a new Authenticator using File storage by key expiration.")
|
||||||
|
{
|
||||||
|
for i, tt := range authTests {
|
||||||
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||||
|
{
|
||||||
|
a, err := auth.NewAuthenticatorFile("", tt.now, tt.keyExpiration)
|
||||||
|
if err != tt.error {
|
||||||
|
t.Log("\t\tGot :", err)
|
||||||
|
t.Log("\t\tWant:", tt.error)
|
||||||
|
t.Fatalf("\t%s\tNewAuthenticatorFile failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
tknStr, err := a.GenerateToken(signedClaims)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("\t\tGot :", err)
|
||||||
|
t.Fatalf("\t%s\tGenerateToken failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedClaims, err := a.ParseClaims(tknStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("\t\tGot :", err)
|
||||||
|
t.Fatalf("\t%s\tParseClaims failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert expected claims.
|
||||||
|
if exp, got := len(signedClaims.Roles), len(parsedClaims.Roles); exp != got {
|
||||||
|
t.Log("\t\tGot :", got)
|
||||||
|
t.Log("\t\tWant:", exp)
|
||||||
|
t.Fatalf("\t%s\tShould got the same number of roles.", tests.Failed)
|
||||||
|
}
|
||||||
|
if exp, got := signedClaims.Roles[0], parsedClaims.Roles[0]; exp != got {
|
||||||
|
t.Log("\t\tGot :", got)
|
||||||
|
t.Log("\t\tWant:", exp)
|
||||||
|
t.Fatalf("\t%s\tShould got the same role name.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("\t%s\tNewAuthenticatorFile ok.", tests.Success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAuthenticatorAws validates AWS storage.
|
||||||
|
func TestAuthenticatorAws(t *testing.T) {
|
||||||
|
|
||||||
awsSecretID := "jwt-key" + uuid.NewRandom().String()
|
awsSecretID := "jwt-key" + uuid.NewRandom().String()
|
||||||
|
|
||||||
@ -58,16 +122,16 @@ func TestAuthenticator(t *testing.T) {
|
|||||||
Roles: []string{auth.RoleAdmin},
|
Roles: []string{auth.RoleAdmin},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("Given the need to validate initiating a new Authenticator by key expiration.")
|
t.Log("Given the need to validate initiating a new Authenticator using AWS storage by key expiration.")
|
||||||
{
|
{
|
||||||
for i, tt := range authTests {
|
for i, tt := range authTests {
|
||||||
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||||
{
|
{
|
||||||
a, err := auth.NewAuthenticator(test.AwsSession, tt.awsSecretID, tt.now, tt.keyExpiration)
|
a, err := auth.NewAuthenticatorAws(test.AwsSession, tt.awsSecretID, tt.now, tt.keyExpiration)
|
||||||
if err != tt.error {
|
if err != tt.error {
|
||||||
t.Log("\t\tGot :", err)
|
t.Log("\t\tGot :", err)
|
||||||
t.Log("\t\tWant:", tt.error)
|
t.Log("\t\tWant:", tt.error)
|
||||||
t.Fatalf("\t%s\tNewAuthenticator failed.", tests.Failed)
|
t.Fatalf("\t%s\tNewAuthenticatorAws failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
tknStr, err := a.GenerateToken(signedClaims)
|
tknStr, err := a.GenerateToken(signedClaims)
|
||||||
@ -94,7 +158,7 @@ func TestAuthenticator(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tShould got the same role name.", tests.Failed)
|
t.Fatalf("\t%s\tShould got the same role name.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("\t%s\tNewAuthenticator ok.", tests.Success)
|
t.Logf("\t%s\tNewAuthenticatorAws ok.", tests.Success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
const algorithm = "RS256"
|
const algorithm = "RS256"
|
||||||
|
|
||||||
// keyGen creates an x509 private key for signing auth tokens.
|
// keyGen creates an x509 private key for signing auth tokens.
|
||||||
func keyGen() ([]byte, error) {
|
func KeyGen() ([]byte, error) {
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, errors.Wrap(err, "generating keys")
|
return []byte{}, errors.Wrap(err, "generating keys")
|
||||||
|
@ -107,7 +107,7 @@ func NewStorageFile(localDir string, now time.Time, keyExpiration time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Values used to format filename.
|
// Values used to format filename.
|
||||||
filePrefix := "auth_"
|
filePrefix := "sassauth_"
|
||||||
fileExt := ".privatekey"
|
fileExt := ".privatekey"
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(localDir)
|
files, err := ioutil.ReadDir(localDir)
|
||||||
@ -171,14 +171,14 @@ func NewStorageFile(localDir string, now time.Time, keyExpiration time.Duration)
|
|||||||
|
|
||||||
// If there are no keys or the current key needs to be rotated, generate a new key.
|
// If there are no keys or the current key needs to be rotated, generate a new key.
|
||||||
if len(keyContents) == 0 || curKeyId == "" {
|
if len(keyContents) == 0 || curKeyId == "" {
|
||||||
privateKey, err := keyGen()
|
privateKey, err := KeyGen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to generate new private key")
|
return nil, errors.Wrap(err, "failed to generate new private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
kID := uuid.NewRandom().String()
|
kID := uuid.NewRandom().String()
|
||||||
|
|
||||||
fname := fmt.Sprintf("%s_%d_%s%s", filePrefix, now.UTC().Unix(), kID, fileExt)
|
fname := fmt.Sprintf("%s%d_%s%s", filePrefix, now.UTC().Unix(), kID, fileExt)
|
||||||
|
|
||||||
filePath := filepath.Join(localDir, fname)
|
filePath := filepath.Join(localDir, fname)
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ func NewStorageAws(awsSession *session.Session, awsSecretID string, now time.Tim
|
|||||||
// refreshed on instance launch. Could store keys in a kv store and update that value
|
// refreshed on instance launch. Could store keys in a kv store and update that value
|
||||||
// when new keys are generated
|
// when new keys are generated
|
||||||
if len(keyContents) == 0 || curKeyId == "" {
|
if len(keyContents) == 0 || curKeyId == "" {
|
||||||
privateKey, err := keyGen()
|
privateKey, err := KeyGen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to generate new private key")
|
return nil, errors.Wrap(err, "failed to generate new private key")
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,9 @@ type Test struct {
|
|||||||
AwsSession *session.Session
|
AwsSession *session.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag used to disable setting up database.
|
||||||
|
var DisableDb bool
|
||||||
|
|
||||||
// New is the entry point for tests.
|
// New is the entry point for tests.
|
||||||
func New() *Test {
|
func New() *Test {
|
||||||
|
|
||||||
@ -44,51 +47,58 @@ func New() *Test {
|
|||||||
|
|
||||||
awsSession := session.Must(session.NewSession())
|
awsSession := session.Must(session.NewSession())
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Startup Postgres container
|
// Startup Postgres container
|
||||||
|
|
||||||
container, err := docker.StartPostgres(log)
|
var (
|
||||||
if err != nil {
|
masterDB *sqlx.DB
|
||||||
log.Fatalln(err)
|
container *docker.Container
|
||||||
}
|
)
|
||||||
|
if !DisableDb {
|
||||||
// ============================================================
|
var err error
|
||||||
// Configuration
|
container, err = docker.StartPostgres(log)
|
||||||
|
|
||||||
dbHost := fmt.Sprintf("postgres://%s:%s@127.0.0.1:%s/%s?timezone=UTC&sslmode=disable", container.User, container.Pass, container.Port, container.Database)
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Start Postgres
|
|
||||||
|
|
||||||
log.Println("main : Started : Initialize Postgres")
|
|
||||||
var masterDB *sqlx.DB
|
|
||||||
for i := 0; i <= 20; i++ {
|
|
||||||
masterDB, err = sqlx.Open("postgres", dbHost)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the database is ready for queries.
|
// ============================================================
|
||||||
_, err = masterDB.Exec("SELECT 1")
|
// Configuration
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
dbHost := fmt.Sprintf("postgres://%s:%s@127.0.0.1:%s/%s?timezone=UTC&sslmode=disable", container.User, container.Pass, container.Port, container.Database)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Start Postgres
|
||||||
|
|
||||||
|
log.Println("main : Started : Initialize Postgres")
|
||||||
|
for i := 0; i <= 20; i++ {
|
||||||
|
masterDB, err = sqlx.Open("postgres", dbHost)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the database is ready for queries.
|
||||||
|
_, err = masterDB.Exec("SELECT 1")
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("startup : Register DB : %v", err)
|
log.Fatalf("startup : Register DB : %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the migrations
|
// Execute the migrations
|
||||||
if err = schema.Migrate(masterDB, log); err != nil {
|
if err = schema.Migrate(masterDB, log); err != nil {
|
||||||
log.Fatalf("main : Migrate : %v", err)
|
log.Fatalf("main : Migrate : %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("main : Migrate : Completed")
|
||||||
}
|
}
|
||||||
log.Printf("main : Migrate : Completed")
|
|
||||||
|
|
||||||
return &Test{log, masterDB, container, awsSession}
|
return &Test{log, masterDB, container, awsSession}
|
||||||
}
|
}
|
||||||
@ -96,9 +106,14 @@ func New() *Test {
|
|||||||
// TearDown is used for shutting down tests. Calling this should be
|
// TearDown is used for shutting down tests. Calling this should be
|
||||||
// done in a defer immediately after calling New.
|
// done in a defer immediately after calling New.
|
||||||
func (t *Test) TearDown() {
|
func (t *Test) TearDown() {
|
||||||
t.MasterDB.Close()
|
if t.MasterDB != nil {
|
||||||
if err := docker.StopPostgres(t.Log, t.container); err != nil {
|
t.MasterDB.Close()
|
||||||
t.Log.Println(err)
|
}
|
||||||
|
|
||||||
|
if t.container != nil {
|
||||||
|
if err := docker.StopPostgres(t.Log, t.container); err != nil {
|
||||||
|
t.Log.Println(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ func migrationList(db *sqlx.DB, log *log.Logger) []*sqlxmigrate.Migration {
|
|||||||
zipcode varchar(20) NOT NULL DEFAULT '',
|
zipcode varchar(20) NOT NULL DEFAULT '',
|
||||||
status account_status_t NOT NULL DEFAULT 'active',
|
status account_status_t NOT NULL DEFAULT 'active',
|
||||||
timezone varchar(128) NOT NULL DEFAULT 'America/Anchorage',
|
timezone varchar(128) NOT NULL DEFAULT 'America/Anchorage',
|
||||||
signup_user_id char(36) DEFAULT NULL REFERENCES users(id),
|
signup_user_id char(36) DEFAULT NULL REFERENCES users(id) ON DELETE SET NULL,
|
||||||
billing_user_id char(36) DEFAULT NULL REFERENCES users(id),
|
billing_user_id char(36) DEFAULT NULL REFERENCES users(id) ON DELETE SET NULL,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
||||||
archived_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
archived_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
||||||
@ -93,7 +93,7 @@ func migrationList(db *sqlx.DB, log *log.Logger) []*sqlxmigrate.Migration {
|
|||||||
},
|
},
|
||||||
// create new table user_accounts
|
// create new table user_accounts
|
||||||
{
|
{
|
||||||
ID: "20190522-01c",
|
ID: "20190522-01d",
|
||||||
Migrate: func(tx *sql.Tx) error {
|
Migrate: func(tx *sql.Tx) error {
|
||||||
q1 := `CREATE TYPE user_account_role_t as enum('admin', 'user')`
|
q1 := `CREATE TYPE user_account_role_t as enum('admin', 'user')`
|
||||||
if _, err := tx.Exec(q1); err != nil {
|
if _, err := tx.Exec(q1); err != nil {
|
||||||
@ -107,15 +107,15 @@ func migrationList(db *sqlx.DB, log *log.Logger) []*sqlxmigrate.Migration {
|
|||||||
|
|
||||||
q3 := `CREATE TABLE IF NOT EXISTS users_accounts (
|
q3 := `CREATE TABLE IF NOT EXISTS users_accounts (
|
||||||
id char(36) NOT NULL,
|
id char(36) NOT NULL,
|
||||||
account_id char(36) NOT NULL REFERENCES accounts(id),
|
account_id char(36) NOT NULL REFERENCES accounts(id) ON DELETE NO ACTION,
|
||||||
user_id char(36) NOT NULL REFERENCES users(id),
|
user_id char(36) NOT NULL REFERENCES users(id) ON DELETE NO ACTION,
|
||||||
roles user_account_role_t[] NOT NULL,
|
roles user_account_role_t[] NOT NULL,
|
||||||
status user_account_status_t NOT NULL DEFAULT 'active',
|
status user_account_status_t NOT NULL DEFAULT 'active',
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
||||||
archived_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
archived_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT user_account UNIQUE (user_id,account_id)
|
CONSTRAINT user_account UNIQUE (user_id,account_id)
|
||||||
)`
|
)`
|
||||||
if _, err := tx.Exec(q3); err != nil {
|
if _, err := tx.Exec(q3); err != nil {
|
||||||
return errors.WithMessagef(err, "Query failed %s", q3)
|
return errors.WithMessagef(err, "Query failed %s", q3)
|
||||||
@ -153,7 +153,7 @@ func migrationList(db *sqlx.DB, log *log.Logger) []*sqlxmigrate.Migration {
|
|||||||
|
|
||||||
q2 := `CREATE TABLE IF NOT EXISTS projects (
|
q2 := `CREATE TABLE IF NOT EXISTS projects (
|
||||||
id char(36) NOT NULL,
|
id char(36) NOT NULL,
|
||||||
account_id char(36) NOT NULL REFERENCES accounts(id),
|
account_id char(36) NOT NULL REFERENCES accounts(id) ON DELETE SET NULL,
|
||||||
name varchar(255) NOT NULL,
|
name varchar(255) NOT NULL,
|
||||||
status project_status_t NOT NULL DEFAULT 'active',
|
status project_status_t NOT NULL DEFAULT 'active',
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
18
example-project/internal/signup/models.go
Normal file
18
example-project/internal/signup/models.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package signup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/account"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignupRequest contains information needed perform signup.
|
||||||
|
type SignupRequest struct {
|
||||||
|
Account account.AccountCreateRequest `json:"account" validate:"required"`
|
||||||
|
User user.UserCreateRequest `json:"user" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignupResponse contains information needed perform signup.
|
||||||
|
type SignupResponse struct {
|
||||||
|
Account *account.Account `json:"account"`
|
||||||
|
User *user.User `json:"user"`
|
||||||
|
}
|
98
example-project/internal/signup/signup.go
Normal file
98
example-project/internal/signup/signup.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package signup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/account"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user_account"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||||
|
"gopkg.in/go-playground/validator.v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signup performs the steps needed to create a new account, new user and then associate
|
||||||
|
// both records with a new user_account entry.
|
||||||
|
func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req SignupRequest, now time.Time) (*SignupResponse, error) {
|
||||||
|
span, ctx := tracer.StartSpanFromContext(ctx, "internal.signup.Signup")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// Default account status to active for signup if now set.
|
||||||
|
if req.Account.Status == nil {
|
||||||
|
s := account.AccountStatus_Active
|
||||||
|
req.Account.Status = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
// Validate the user email address is unique in the database.
|
||||||
|
uniqEmail, err := user.UniqueEmail(ctx, dbConn, req.User.Email, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the account name is unique in the database.
|
||||||
|
uniqName, err := account.UniqueName(ctx, dbConn, req.Account.Name, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := func(fl validator.FieldLevel) bool {
|
||||||
|
if fl.Field().String() == "invalid" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var uniq bool
|
||||||
|
switch (fl.FieldName()) {
|
||||||
|
case "Name":
|
||||||
|
uniq = uniqName
|
||||||
|
case "Email":
|
||||||
|
uniq = uniqEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniq
|
||||||
|
}
|
||||||
|
v.RegisterValidation("unique", f)
|
||||||
|
|
||||||
|
// Validate the request.
|
||||||
|
err = v.Struct(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp SignupResponse
|
||||||
|
|
||||||
|
// Execute user creation.
|
||||||
|
resp.User, err = user.Create(ctx, claims, dbConn, req.User, now)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the signup and billing user IDs for reference.
|
||||||
|
req.Account.SignupUserID = &resp.User.ID
|
||||||
|
req.Account.BillingUserID = &resp.User.ID
|
||||||
|
|
||||||
|
// Execute account creation.
|
||||||
|
resp.Account, err = account.Create(ctx, claims, dbConn, req.Account, now)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate the created user with the new account. The first user for the account will
|
||||||
|
// always have the role of admin.
|
||||||
|
ua := user_account.CreateUserAccountRequest{
|
||||||
|
UserID: resp.User.ID,
|
||||||
|
AccountID: resp.Account.ID,
|
||||||
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole_Admin},
|
||||||
|
//Status: Use default value
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = user_account.Create(ctx, claims, dbConn, ua, now)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
172
example-project/internal/signup/signup_test.go
Normal file
172
example-project/internal/signup/signup_test.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package signup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/account"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user"
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var test *tests.Test
|
||||||
|
|
||||||
|
// TestMain is the entry point for testing.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(testMain(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMain(m *testing.M) int {
|
||||||
|
test = tests.New()
|
||||||
|
defer test.TearDown()
|
||||||
|
return m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TestSignupValidation ensures all the validation tags work on Signup
|
||||||
|
func TestSignupValidation(t *testing.T) {
|
||||||
|
|
||||||
|
var userTests = []struct {
|
||||||
|
name string
|
||||||
|
req SignupRequest
|
||||||
|
expected func(req SignupRequest, res *SignupResponse) *SignupResponse
|
||||||
|
error error
|
||||||
|
}{
|
||||||
|
{"Required Fields",
|
||||||
|
SignupRequest{},
|
||||||
|
func(req SignupRequest, res *SignupResponse) *SignupResponse {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
errors.New("Key: 'SignupRequest.Account.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.Account.Address1' Error:Field validation for 'Address1' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.Account.City' Error:Field validation for 'City' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.Account.Region' Error:Field validation for 'Region' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.Account.Country' Error:Field validation for 'Country' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.Account.Zipcode' Error:Field validation for 'Zipcode' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.User.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.User.Email' Error:Field validation for 'Email' failed on the 'required' tag\n" +
|
||||||
|
"Key: 'SignupRequest.User.Password' Error:Field validation for 'Password' failed on the 'required' tag"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
t.Log("Given the need ensure all validation tags are working for signup.")
|
||||||
|
{
|
||||||
|
for i, tt := range userTests {
|
||||||
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||||
|
{
|
||||||
|
ctx := tests.Context()
|
||||||
|
|
||||||
|
res, err := Signup(ctx, auth.Claims{}, test.MasterDB, tt.req, now)
|
||||||
|
if err != tt.error {
|
||||||
|
// TODO: need a better way to handle validation errors as they are
|
||||||
|
// of type interface validator.ValidationErrorsTranslations
|
||||||
|
var errStr string
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
var expectStr string
|
||||||
|
if tt.error != nil {
|
||||||
|
expectStr = tt.error.Error()
|
||||||
|
}
|
||||||
|
if errStr != expectStr {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Logf("\t\tWant: %+v", tt.error)
|
||||||
|
t.Fatalf("\t%s\tSignup failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was an error that was expected, then don't go any further
|
||||||
|
if tt.error != nil {
|
||||||
|
t.Logf("\t%s\tSignup ok.", tests.Success)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := tt.expected(tt.req, res)
|
||||||
|
if diff := cmp.Diff(res, expected); diff != "" {
|
||||||
|
t.Fatalf("\t%s\tExpected result should match. Diff:\n%s", tests.Failed, diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("\t%s\tSignup ok.", tests.Success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSignupFull validates Signup and ensures the created user can login.
|
||||||
|
func TestSignupFull(t *testing.T) {
|
||||||
|
|
||||||
|
req := SignupRequest{
|
||||||
|
Account: account.AccountCreateRequest{
|
||||||
|
Name: uuid.NewRandom().String(),
|
||||||
|
Address1: "103 East Main St",
|
||||||
|
Address2: "Unit 546",
|
||||||
|
City: "Valdez",
|
||||||
|
Region: "AK",
|
||||||
|
Country: "USA",
|
||||||
|
Zipcode: "99686",
|
||||||
|
},
|
||||||
|
User: user.UserCreateRequest{
|
||||||
|
Name: "Lee Brown",
|
||||||
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
|
Password: "akTechFr0n!ier",
|
||||||
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := tests.Context()
|
||||||
|
|
||||||
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
tknGen := &user.MockTokenGenerator{}
|
||||||
|
|
||||||
|
t.Log("Given the need to ensure signup works.")
|
||||||
|
{
|
||||||
|
res, err := Signup(ctx, auth.Claims{}, test.MasterDB, req, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot error : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tSignup failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.User == nil || res.User.ID == "" {
|
||||||
|
t.Fatalf("\t%s\tResponse user is empty.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Account == nil || res.Account.ID == "" {
|
||||||
|
t.Fatalf("\t%s\tResponse account is empty.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Account.SignupUserID.String == "" {
|
||||||
|
t.Fatalf("\t%s\tResponse account signup user ID is empty.", tests.Failed)
|
||||||
|
} else if res.Account.SignupUserID.String != res.User.ID {
|
||||||
|
t.Logf("\t\tGot : %+v", res.Account.SignupUserID.String)
|
||||||
|
t.Logf("\t\tWant: %+v", res.User.ID)
|
||||||
|
t.Fatalf("\t%s\tSigup user ID does not match created user ID.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Account.BillingUserID.String == "" {
|
||||||
|
t.Fatalf("\t%s\tResponse account billing user ID is empty.", tests.Failed)
|
||||||
|
} else if res.Account.BillingUserID.String != res.User.ID {
|
||||||
|
t.Logf("\t\tGot : %+v", res.Account.BillingUserID.String)
|
||||||
|
t.Logf("\t\tWant: %+v", res.User.ID)
|
||||||
|
t.Fatalf("\t%s\tBilling user ID does not match created user ID.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("\t%s\tSignup ok.", tests.Success)
|
||||||
|
|
||||||
|
|
||||||
|
// Verify that the user can be authenticated with the updated password.
|
||||||
|
_, err = user.Authenticate(ctx, test.MasterDB, tknGen, res.User.Email, req.User.Password, time.Hour, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("\t\tGot :", err)
|
||||||
|
t.Fatalf("\t%s\tAuthenticate failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
t.Logf("\t%s\tAuthenticate ok.", tests.Success)
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||||
@ -224,3 +226,68 @@ func generateToken(ctx context.Context, dbConn *sqlx.DB, tknGen TokenGenerator,
|
|||||||
|
|
||||||
return Token{Token: tkn, claims: claims}, nil
|
return Token{Token: tkn, claims: claims}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// mockTokenGenerator is used for testing that Authenticate calls its provided
|
||||||
|
// token generator in a specific way.
|
||||||
|
type MockTokenGenerator struct {
|
||||||
|
// Private key generated by GenerateToken that is need for ParseClaims
|
||||||
|
key *rsa.PrivateKey
|
||||||
|
// algorithm is the method used to generate the private key.
|
||||||
|
algorithm string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateToken implements the TokenGenerator interface. It returns a "token"
|
||||||
|
// that includes some information about the claims it was passed.
|
||||||
|
func (g *MockTokenGenerator) GenerateToken(claims auth.Claims) (string, error) {
|
||||||
|
privateKey, err := auth.KeyGen()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.key, err = jwt.ParseRSAPrivateKeyFromPEM(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.algorithm = "RS256"
|
||||||
|
method := jwt.GetSigningMethod(g.algorithm)
|
||||||
|
|
||||||
|
tkn := jwt.NewWithClaims(method, claims)
|
||||||
|
tkn.Header["kid"] = "1"
|
||||||
|
|
||||||
|
str, err := tkn.SignedString(g.key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseClaims recreates the Claims that were used to generate a token. It
|
||||||
|
// verifies that the token was signed using our key.
|
||||||
|
func (g *MockTokenGenerator) ParseClaims(tknStr string) (auth.Claims, error) {
|
||||||
|
parser := jwt.Parser{
|
||||||
|
ValidMethods: []string{g.algorithm},
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.key == nil {
|
||||||
|
return auth.Claims{}, errors.New("Private key is empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := func(t *jwt.Token) (interface{}, error) {
|
||||||
|
return g.key.Public().(*rsa.PublicKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims auth.Claims
|
||||||
|
tkn, err := parser.ParseWithClaims(tknStr, &claims, f)
|
||||||
|
if err != nil {
|
||||||
|
return auth.Claims{}, errors.Wrap(err, "parsing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tkn.Valid {
|
||||||
|
return auth.Claims{}, errors.New("Invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
@ -1,82 +1,16 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
|
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mockTokenGenerator is used for testing that Authenticate calls its provided
|
|
||||||
// token generator in a specific way.
|
|
||||||
type mockTokenGenerator struct {
|
|
||||||
// Private key generated by GenerateToken that is need for ParseClaims
|
|
||||||
key *rsa.PrivateKey
|
|
||||||
// algorithm is the method used to generate the private key.
|
|
||||||
algorithm string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateToken implements the TokenGenerator interface. It returns a "token"
|
|
||||||
// that includes some information about the claims it was passed.
|
|
||||||
func (g *mockTokenGenerator) GenerateToken(claims auth.Claims) (string, error) {
|
|
||||||
privateKey, err := auth.Keygen()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
g.key, err = jwt.ParseRSAPrivateKeyFromPEM(privateKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
g.algorithm = "RS256"
|
|
||||||
method := jwt.GetSigningMethod(g.algorithm)
|
|
||||||
|
|
||||||
tkn := jwt.NewWithClaims(method, claims)
|
|
||||||
tkn.Header["kid"] = "1"
|
|
||||||
|
|
||||||
str, err := tkn.SignedString(g.key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseClaims recreates the Claims that were used to generate a token. It
|
|
||||||
// verifies that the token was signed using our key.
|
|
||||||
func (g *mockTokenGenerator) ParseClaims(tknStr string) (auth.Claims, error) {
|
|
||||||
parser := jwt.Parser{
|
|
||||||
ValidMethods: []string{g.algorithm},
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.key == nil {
|
|
||||||
return auth.Claims{}, errors.New("Private key is empty.")
|
|
||||||
}
|
|
||||||
|
|
||||||
f := func(t *jwt.Token) (interface{}, error) {
|
|
||||||
return g.key.Public().(*rsa.PublicKey), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var claims auth.Claims
|
|
||||||
tkn, err := parser.ParseWithClaims(tknStr, &claims, f)
|
|
||||||
if err != nil {
|
|
||||||
return auth.Claims{}, errors.Wrap(err, "parsing token")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tkn.Valid {
|
|
||||||
return auth.Claims{}, errors.New("Invalid token")
|
|
||||||
}
|
|
||||||
|
|
||||||
return claims, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAuthenticate validates the behavior around authenticating users.
|
// TestAuthenticate validates the behavior around authenticating users.
|
||||||
func TestAuthenticate(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
defer tests.Recover(t)
|
defer tests.Recover(t)
|
||||||
@ -87,7 +21,7 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
tknGen := &mockTokenGenerator{}
|
tknGen := &MockTokenGenerator{}
|
||||||
|
|
||||||
// Auth tokens are valid for an our and is verified against current time.
|
// Auth tokens are valid for an our and is verified against current time.
|
||||||
// Issue the token one hour ago.
|
// Issue the token one hour ago.
|
||||||
@ -104,7 +38,7 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
|
|
||||||
// Create a new user for testing.
|
// Create a new user for testing.
|
||||||
initPass := uuid.NewRandom().String()
|
initPass := uuid.NewRandom().String()
|
||||||
user, err := Create(ctx, auth.Claims{}, test.MasterDB, CreateUserRequest{
|
user, err := Create(ctx, auth.Claims{}, test.MasterDB, UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: initPass,
|
Password: initPass,
|
||||||
@ -132,7 +66,7 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a second new random account.
|
// Create a second new random account. Need to ensure
|
||||||
account2Id := uuid.NewRandom().String()
|
account2Id := uuid.NewRandom().String()
|
||||||
err = mockAccount(account2Id, user.CreatedAt)
|
err = mockAccount(account2Id, user.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -140,9 +74,11 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Associate secoend new account with user user.
|
// Associate second new account with user user. Need to ensure that now
|
||||||
|
// is always greater than the first user_account entry created so it will
|
||||||
|
// be returned consistently back in the same order, last.
|
||||||
account2Role := auth.RoleUser
|
account2Role := auth.RoleUser
|
||||||
err = mockUserAccount(user.ID, account2Id, user.CreatedAt, account2Role)
|
err = mockUserAccount(user.ID, account2Id, user.CreatedAt.Add(time.Second), account2Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("\t\tGot :", err)
|
t.Log("\t\tGot :", err)
|
||||||
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
||||||
|
@ -232,7 +232,7 @@ func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validation an email address is unique excluding the current user ID.
|
// Validation an email address is unique excluding the current user ID.
|
||||||
func uniqueEmail(ctx context.Context, dbConn *sqlx.DB, email, userId string) (bool, error) {
|
func UniqueEmail(ctx context.Context, dbConn *sqlx.DB, email, userId string) (bool, error) {
|
||||||
query := sqlbuilder.NewSelectBuilder().Select("id").From(userTableName)
|
query := sqlbuilder.NewSelectBuilder().Select("id").From(userTableName)
|
||||||
query.Where(query.And(
|
query.Where(query.And(
|
||||||
query.Equal("email", email),
|
query.Equal("email", email),
|
||||||
@ -264,7 +264,7 @@ func Create(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req UserCr
|
|||||||
v := validator.New()
|
v := validator.New()
|
||||||
|
|
||||||
// Validation email address is unique in the database.
|
// Validation email address is unique in the database.
|
||||||
uniq, err := uniqueEmail(ctx, dbConn, req.Email, "")
|
uniq, err := UniqueEmail(ctx, dbConn, req.Email, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -376,7 +376,7 @@ func Update(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req UserUp
|
|||||||
|
|
||||||
// Validation email address is unique in the database.
|
// Validation email address is unique in the database.
|
||||||
if req.Email != nil {
|
if req.Email != nil {
|
||||||
uniq, err := uniqueEmail(ctx, dbConn, *req.Email, req.ID)
|
uniq, err := UniqueEmail(ctx, dbConn, *req.Email, req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -614,22 +614,14 @@ func Delete(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, userID str
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the delete SQL statement.
|
// Start a new transaction to handle rollbacks on error.
|
||||||
query := sqlbuilder.NewDeleteBuilder()
|
tx, err := dbConn.Begin()
|
||||||
query.DeleteFrom(userTableName)
|
|
||||||
query.Where(query.Equal("id", req.ID))
|
|
||||||
|
|
||||||
// Execute the query with the provided context.
|
|
||||||
sql, args := query.Build()
|
|
||||||
sql = dbConn.Rebind(sql)
|
|
||||||
_, err = dbConn.ExecContext(ctx, sql, args...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrapf(err, "query - %s", query.String())
|
return errors.WithStack(err)
|
||||||
err = errors.WithMessagef(err, "delete user %s failed", req.ID)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all the associated user accounts
|
// Delete all the associated user accounts.
|
||||||
|
// Required to execute first to avoid foreign key constraints.
|
||||||
{
|
{
|
||||||
// Build the delete SQL statement.
|
// Build the delete SQL statement.
|
||||||
query := sqlbuilder.NewDeleteBuilder()
|
query := sqlbuilder.NewDeleteBuilder()
|
||||||
@ -641,13 +633,37 @@ func Delete(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, userID str
|
|||||||
// Execute the query with the provided context.
|
// Execute the query with the provided context.
|
||||||
sql, args := query.Build()
|
sql, args := query.Build()
|
||||||
sql = dbConn.Rebind(sql)
|
sql = dbConn.Rebind(sql)
|
||||||
_, err = dbConn.ExecContext(ctx, sql, args...)
|
_, err = tx.ExecContext(ctx, sql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
|
||||||
err = errors.Wrapf(err, "query - %s", query.String())
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
err = errors.WithMessagef(err, "delete accounts for user %s failed", req.ID)
|
err = errors.WithMessagef(err, "delete accounts for user %s failed", req.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the delete SQL statement.
|
||||||
|
query := sqlbuilder.NewDeleteBuilder()
|
||||||
|
query.DeleteFrom(userTableName)
|
||||||
|
query.Where(query.Equal("id", req.ID))
|
||||||
|
|
||||||
|
// Execute the query with the provided context.
|
||||||
|
sql, args := query.Build()
|
||||||
|
sql = dbConn.Rebind(sql)
|
||||||
|
_, err = tx.ExecContext(ctx, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
err = errors.WithMessagef(err, "delete user %s failed", req.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -140,51 +140,51 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
|
|
||||||
var userTests = []struct {
|
var userTests = []struct {
|
||||||
name string
|
name string
|
||||||
req CreateUserRequest
|
req UserCreateRequest
|
||||||
expected func(req CreateUserRequest, res *User) *User
|
expected func(req UserCreateRequest, res *User) *User
|
||||||
error error
|
error error
|
||||||
}{
|
}{
|
||||||
{"Required Fields",
|
{"Required Fields",
|
||||||
CreateUserRequest{},
|
UserCreateRequest{},
|
||||||
func(req CreateUserRequest, res *User) *User {
|
func(req UserCreateRequest, res *User) *User {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
errors.New("Key: 'CreateUserRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
errors.New("Key: 'UserCreateRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateUserRequest.Email' Error:Field validation for 'Email' failed on the 'required' tag\n" +
|
"Key: 'UserCreateRequest.Email' Error:Field validation for 'Email' failed on the 'required' tag\n" +
|
||||||
"Key: 'CreateUserRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag"),
|
"Key: 'UserCreateRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag"),
|
||||||
},
|
},
|
||||||
{"Valid Email",
|
{"Valid Email",
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: "xxxxxxxxxx",
|
Email: "xxxxxxxxxx",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(req CreateUserRequest, res *User) *User {
|
func(req UserCreateRequest, res *User) *User {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
errors.New("Key: 'CreateUserRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag"),
|
errors.New("Key: 'UserCreateRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag"),
|
||||||
},
|
},
|
||||||
{"Passwords Match",
|
{"Passwords Match",
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "W0rkL1fe#",
|
PasswordConfirm: "W0rkL1fe#",
|
||||||
},
|
},
|
||||||
func(req CreateUserRequest, res *User) *User {
|
func(req UserCreateRequest, res *User) *User {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
errors.New("Key: 'CreateUserRequest.PasswordConfirm' Error:Field validation for 'PasswordConfirm' failed on the 'eqfield' tag"),
|
errors.New("Key: 'UserCreateRequest.PasswordConfirm' Error:Field validation for 'PasswordConfirm' failed on the 'eqfield' tag"),
|
||||||
},
|
},
|
||||||
{"Default Timezone",
|
{"Default Timezone",
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(req CreateUserRequest, res *User) *User {
|
func(req UserCreateRequest, res *User) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
@ -258,7 +258,7 @@ func TestCreateValidationEmailUnique(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
req1 := CreateUserRequest{
|
req1 := UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
@ -270,13 +270,13 @@ func TestCreateValidationEmailUnique(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
req2 := CreateUserRequest{
|
req2 := UserCreateRequest{
|
||||||
Name: "Lucas Brown",
|
Name: "Lucas Brown",
|
||||||
Email: user1.Email,
|
Email: user1.Email,
|
||||||
Password: "W0rkL1fe#",
|
Password: "W0rkL1fe#",
|
||||||
PasswordConfirm: "W0rkL1fe#",
|
PasswordConfirm: "W0rkL1fe#",
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("Key: 'CreateUserRequest.Email' Error:Field validation for 'Email' failed on the 'unique' tag")
|
expectedErr := errors.New("Key: 'UserCreateRequest.Email' Error:Field validation for 'Email' failed on the 'unique' tag")
|
||||||
_, err = Create(ctx, auth.Claims{}, test.MasterDB, req2, now)
|
_, err = Create(ctx, auth.Claims{}, test.MasterDB, req2, now)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("\t\tWant: %+v", expectedErr)
|
t.Logf("\t\tWant: %+v", expectedErr)
|
||||||
@ -300,13 +300,13 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
var userTests = []struct {
|
var userTests = []struct {
|
||||||
name string
|
name string
|
||||||
claims auth.Claims
|
claims auth.Claims
|
||||||
req CreateUserRequest
|
req UserCreateRequest
|
||||||
error error
|
error error
|
||||||
}{
|
}{
|
||||||
// Internal request, should bypass ACL.
|
// Internal request, should bypass ACL.
|
||||||
{"EmptyClaims",
|
{"EmptyClaims",
|
||||||
auth.Claims{},
|
auth.Claims{},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
@ -323,7 +323,7 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
Audience: "acc1",
|
Audience: "acc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
@ -340,7 +340,7 @@ func TestCreateClaims(t *testing.T) {
|
|||||||
Audience: "acc1",
|
Audience: "acc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
@ -377,24 +377,24 @@ func TestUpdateValidation(t *testing.T) {
|
|||||||
// TODO: actually create the user so can test the output of findbyId
|
// TODO: actually create the user so can test the output of findbyId
|
||||||
type userTest struct {
|
type userTest struct {
|
||||||
name string
|
name string
|
||||||
req UpdateUserRequest
|
req UserUpdateRequest
|
||||||
error error
|
error error
|
||||||
}
|
}
|
||||||
|
|
||||||
var userTests = []userTest{
|
var userTests = []userTest{
|
||||||
{"Required Fields",
|
{"Required Fields",
|
||||||
UpdateUserRequest{},
|
UserUpdateRequest{},
|
||||||
errors.New("Key: 'UpdateUserRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag"),
|
errors.New("Key: 'UserUpdateRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidEmail := "xxxxxxxxxx"
|
invalidEmail := "xxxxxxxxxx"
|
||||||
userTests = append(userTests, userTest{"Valid Email",
|
userTests = append(userTests, userTest{"Valid Email",
|
||||||
UpdateUserRequest{
|
UserUpdateRequest{
|
||||||
ID: uuid.NewRandom().String(),
|
ID: uuid.NewRandom().String(),
|
||||||
Email: &invalidEmail,
|
Email: &invalidEmail,
|
||||||
},
|
},
|
||||||
errors.New("Key: 'UpdateUserRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag"),
|
errors.New("Key: 'UserUpdateRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag"),
|
||||||
})
|
})
|
||||||
|
|
||||||
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
||||||
@ -440,7 +440,7 @@ func TestUpdateValidationEmailUnique(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
req1 := CreateUserRequest{
|
req1 := UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
@ -452,7 +452,7 @@ func TestUpdateValidationEmailUnique(t *testing.T) {
|
|||||||
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
req2 := CreateUserRequest{
|
req2 := UserCreateRequest{
|
||||||
Name: "Lucas Brown",
|
Name: "Lucas Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "W0rkL1fe#",
|
Password: "W0rkL1fe#",
|
||||||
@ -465,11 +465,11 @@ func TestUpdateValidationEmailUnique(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to set the email for user 1 on user 2
|
// Try to set the email for user 1 on user 2
|
||||||
updateReq := UpdateUserRequest{
|
updateReq := UserUpdateRequest{
|
||||||
ID: user2.ID,
|
ID: user2.ID,
|
||||||
Email: &user1.Email,
|
Email: &user1.Email,
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("Key: 'UpdateUserRequest.Email' Error:Field validation for 'Email' failed on the 'unique' tag")
|
expectedErr := errors.New("Key: 'UserUpdateRequest.Email' Error:Field validation for 'Email' failed on the 'unique' tag")
|
||||||
err = Update(ctx, auth.Claims{}, test.MasterDB, updateReq, now)
|
err = Update(ctx, auth.Claims{}, test.MasterDB, updateReq, now)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("\t\tWant: %+v", expectedErr)
|
t.Logf("\t\tWant: %+v", expectedErr)
|
||||||
@ -495,11 +495,11 @@ func TestUpdatePassword(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
tknGen := &mockTokenGenerator{}
|
tknGen := &MockTokenGenerator{}
|
||||||
|
|
||||||
// Create a new user for testing.
|
// Create a new user for testing.
|
||||||
initPass := uuid.NewRandom().String()
|
initPass := uuid.NewRandom().String()
|
||||||
user, err := Create(ctx, auth.Claims{}, test.MasterDB, CreateUserRequest{
|
user, err := Create(ctx, auth.Claims{}, test.MasterDB, UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: initPass,
|
Password: initPass,
|
||||||
@ -533,9 +533,9 @@ func TestUpdatePassword(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure validation is working by trying UpdatePassword with an empty request.
|
// Ensure validation is working by trying UpdatePassword with an empty request.
|
||||||
expectedErr := errors.New("Key: 'UpdatePasswordRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag\n" +
|
expectedErr := errors.New("Key: 'UserUpdatePasswordRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag\n" +
|
||||||
"Key: 'UpdatePasswordRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag")
|
"Key: 'UserUpdatePasswordRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag")
|
||||||
err = UpdatePassword(ctx, auth.Claims{}, test.MasterDB, UpdatePasswordRequest{}, now)
|
err = UpdatePassword(ctx, auth.Claims{}, test.MasterDB, UserUpdatePasswordRequest{}, now)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("\t\tWant: %+v", expectedErr)
|
t.Logf("\t\tWant: %+v", expectedErr)
|
||||||
t.Fatalf("\t%s\tUpdate failed.", tests.Failed)
|
t.Fatalf("\t%s\tUpdate failed.", tests.Failed)
|
||||||
@ -548,7 +548,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||||||
|
|
||||||
// Update the users password.
|
// Update the users password.
|
||||||
newPass := uuid.NewRandom().String()
|
newPass := uuid.NewRandom().String()
|
||||||
err = UpdatePassword(ctx, auth.Claims{}, test.MasterDB, UpdatePasswordRequest{
|
err = UpdatePassword(ctx, auth.Claims{}, test.MasterDB, UserUpdatePasswordRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Password: newPass,
|
Password: newPass,
|
||||||
PasswordConfirm: newPass,
|
PasswordConfirm: newPass,
|
||||||
@ -576,10 +576,10 @@ func TestCrud(t *testing.T) {
|
|||||||
type userTest struct {
|
type userTest struct {
|
||||||
name string
|
name string
|
||||||
claims func(*User, string) auth.Claims
|
claims func(*User, string) auth.Claims
|
||||||
create CreateUserRequest
|
create UserCreateRequest
|
||||||
update func(*User) UpdateUserRequest
|
update func(*User) UserUpdateRequest
|
||||||
updateErr error
|
updateErr error
|
||||||
expected func(*User, UpdateUserRequest) *User
|
expected func(*User, UserUpdateRequest) *User
|
||||||
findErr error
|
findErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,21 +590,21 @@ func TestCrud(t *testing.T) {
|
|||||||
func(user *User, accountId string) auth.Claims {
|
func(user *User, accountId string) auth.Claims {
|
||||||
return auth.Claims{}
|
return auth.Claims{}
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(user *User) UpdateUserRequest {
|
func(user *User) UserUpdateRequest {
|
||||||
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
||||||
return UpdateUserRequest{
|
return UserUpdateRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(user *User, req UpdateUserRequest) *User {
|
func(user *User, req UserUpdateRequest) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Email: *req.Email,
|
Email: *req.Email,
|
||||||
// Copy this fields from the created user.
|
// Copy this fields from the created user.
|
||||||
@ -633,21 +633,21 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(user *User) UpdateUserRequest {
|
func(user *User) UserUpdateRequest {
|
||||||
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
||||||
return UpdateUserRequest{
|
return UserUpdateRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ErrForbidden,
|
ErrForbidden,
|
||||||
func(user *User, req UpdateUserRequest) *User {
|
func(user *User, req UserUpdateRequest) *User {
|
||||||
return user
|
return user
|
||||||
},
|
},
|
||||||
ErrNotFound,
|
ErrNotFound,
|
||||||
@ -664,21 +664,21 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(user *User) UpdateUserRequest {
|
func(user *User) UserUpdateRequest {
|
||||||
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
||||||
return UpdateUserRequest{
|
return UserUpdateRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(user *User, req UpdateUserRequest) *User {
|
func(user *User, req UserUpdateRequest) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Email: *req.Email,
|
Email: *req.Email,
|
||||||
// Copy this fields from the created user.
|
// Copy this fields from the created user.
|
||||||
@ -707,21 +707,21 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(user *User) UpdateUserRequest {
|
func(user *User) UserUpdateRequest {
|
||||||
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
||||||
return UpdateUserRequest{
|
return UserUpdateRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ErrForbidden,
|
ErrForbidden,
|
||||||
func(user *User, req UpdateUserRequest) *User {
|
func(user *User, req UserUpdateRequest) *User {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
ErrNotFound,
|
ErrNotFound,
|
||||||
@ -738,21 +738,21 @@ func TestCrud(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateUserRequest{
|
UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
},
|
},
|
||||||
func(user *User) UpdateUserRequest {
|
func(user *User) UserUpdateRequest {
|
||||||
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
email := uuid.NewRandom().String() + "@geeksinthewoods.com"
|
||||||
return UpdateUserRequest{
|
return UserUpdateRequest{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
func(user *User, req UpdateUserRequest) *User {
|
func(user *User, req UserUpdateRequest) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Email: *req.Email,
|
Email: *req.Email,
|
||||||
// Copy this fields from the created user.
|
// Copy this fields from the created user.
|
||||||
@ -784,15 +784,22 @@ func TestCrud(t *testing.T) {
|
|||||||
user, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, tt.create, now)
|
user, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, tt.create, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("\t\tGot :", err)
|
t.Log("\t\tGot :", err)
|
||||||
t.Fatalf("\t%s\tCreate failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate user failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new random account and associate that with the user.
|
// Create a random account for the new user.
|
||||||
accountId := uuid.NewRandom().String()
|
accountId := uuid.NewRandom().String()
|
||||||
|
err = mockAccount(accountId, user.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("\t\tGot :", err)
|
||||||
|
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate the account with the new test user.
|
||||||
err = mockUserAccount(user.ID, accountId, user.CreatedAt, auth.RoleAdmin)
|
err = mockUserAccount(user.ID, accountId, user.CreatedAt, auth.RoleAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("\t\tGot :", err)
|
t.Log("\t\tGot :", err)
|
||||||
t.Fatalf("\t%s\tAdd user account failed.", tests.Failed)
|
t.Fatalf("\t%s\tCreate user account failed.", tests.Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the user.
|
// Update the user.
|
||||||
@ -874,7 +881,7 @@ func TestFind(t *testing.T) {
|
|||||||
|
|
||||||
var users []*User
|
var users []*User
|
||||||
for i := 0; i <= 4; i++ {
|
for i := 0; i <= 4; i++ {
|
||||||
user, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, CreateUserRequest{
|
user, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, UserCreateRequest{
|
||||||
Name: "Lee Brown",
|
Name: "Lee Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package user
|
package user_account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package user
|
package user_account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package user
|
package user_account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
@ -212,6 +212,20 @@ func TestCreateValidation(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
|
// Generate a new random user.
|
||||||
|
err := mockUser(tt.req.UserID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock user failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random account.
|
||||||
|
err = mockAccount(tt.req.AccountID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock account failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
res, err := Create(ctx, auth.Claims{}, test.MasterDB, tt.req, now)
|
res, err := Create(ctx, auth.Claims{}, test.MasterDB, tt.req, now)
|
||||||
if err != tt.error {
|
if err != tt.error {
|
||||||
// TODO: need a better way to handle validation errors as they are
|
// TODO: need a better way to handle validation errors as they are
|
||||||
@ -258,9 +272,25 @@ func TestCreateExistingEntry(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ctx := tests.Context()
|
ctx := tests.Context()
|
||||||
|
|
||||||
|
// Generate a new random user.
|
||||||
|
userID := uuid.NewRandom().String()
|
||||||
|
err := mockUser(userID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock user failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random account.
|
||||||
|
accountID := uuid.NewRandom().String()
|
||||||
|
err = mockAccount(accountID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock account failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
req1 := CreateUserAccountRequest{
|
req1 := CreateUserAccountRequest{
|
||||||
UserID: uuid.NewRandom().String(),
|
UserID: userID,
|
||||||
AccountID: uuid.NewRandom().String(),
|
AccountID: accountID,
|
||||||
Roles: []UserAccountRole{UserAccountRole_User},
|
Roles: []UserAccountRole{UserAccountRole_User},
|
||||||
}
|
}
|
||||||
ua1, err := Create(ctx, auth.Claims{}, test.MasterDB, req1, now)
|
ua1, err := Create(ctx, auth.Claims{}, test.MasterDB, req1, now)
|
||||||
@ -503,9 +533,23 @@ func TestCrud(t *testing.T) {
|
|||||||
for i, tt := range accountTests {
|
for i, tt := range accountTests {
|
||||||
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||||
{
|
{
|
||||||
// Create a new random account and associate that with the user.
|
// Generate a new random user.
|
||||||
userID := uuid.NewRandom().String()
|
userID := uuid.NewRandom().String()
|
||||||
|
err := mockUser(userID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock user failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random account.
|
||||||
accountID := uuid.NewRandom().String()
|
accountID := uuid.NewRandom().String()
|
||||||
|
err = mockAccount(accountID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tMock account failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate that with the user.
|
||||||
createReq := CreateUserAccountRequest{
|
createReq := CreateUserAccountRequest{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
@ -656,9 +700,23 @@ func TestFind(t *testing.T) {
|
|||||||
|
|
||||||
var userAccounts []*UserAccount
|
var userAccounts []*UserAccount
|
||||||
for i := 0; i <= 4; i++ {
|
for i := 0; i <= 4; i++ {
|
||||||
// Create a new random account and associate that with the user.
|
// Generate a new random user.
|
||||||
userID := uuid.NewRandom().String()
|
userID := uuid.NewRandom().String()
|
||||||
|
err := mockUser(userID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tCreate user failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random account.
|
||||||
accountID := uuid.NewRandom().String()
|
accountID := uuid.NewRandom().String()
|
||||||
|
err = mockAccount(accountID, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("\t\tGot : %+v", err)
|
||||||
|
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute Create that will associate the user with the account.
|
||||||
ua, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, CreateUserAccountRequest{
|
ua, err := Create(tests.Context(), auth.Claims{}, test.MasterDB, CreateUserAccountRequest{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
@ -784,3 +842,43 @@ func TestFind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mockAccount(accountId string, now time.Time) error {
|
||||||
|
|
||||||
|
// Build the insert SQL statement.
|
||||||
|
query := sqlbuilder.NewInsertBuilder()
|
||||||
|
query.InsertInto("accounts")
|
||||||
|
query.Cols("id", "name", "created_at", "updated_at")
|
||||||
|
query.Values(accountId, uuid.NewRandom().String(), now, now)
|
||||||
|
|
||||||
|
// Execute the query with the provided context.
|
||||||
|
sql, args := query.Build()
|
||||||
|
sql = test.MasterDB.Rebind(sql)
|
||||||
|
_, err := test.MasterDB.ExecContext(tests.Context(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockUser(userId string, now time.Time) error {
|
||||||
|
|
||||||
|
// Build the insert SQL statement.
|
||||||
|
query := sqlbuilder.NewInsertBuilder()
|
||||||
|
query.InsertInto("users")
|
||||||
|
query.Cols("id", "email", "password_hash", "password_salt", "created_at", "updated_at")
|
||||||
|
query.Values(userId, uuid.NewRandom().String(), "-", "-", now, now)
|
||||||
|
|
||||||
|
// Execute the query with the provided context.
|
||||||
|
sql, args := query.Build()
|
||||||
|
sql = test.MasterDB.Rebind(sql)
|
||||||
|
_, err := test.MasterDB.ExecContext(tests.Context(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user