GithubHelp home page GithubHelp logo

techschool / simplebank Goto Github PK

View Code? Open in Web Editor NEW
4.0K 4.0K 843.0 6.14 MB

Backend master class: build a simple bank service in Go

License: MIT License

Makefile 1.47% Go 95.05% Shell 3.24% Dockerfile 0.25%
backend docker gin go golang grpc grpc-go http-server kubernetes

simplebank's People

Contributors

edwardlee4948 avatar email2vimalraj avatar ghalibansari avatar phamlequang avatar taha-ahmadi avatar techschool avatar xshyamx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simplebank's Issues

Race detected when testing in parallel

First of all thanks for the excellent lecture!

I have already learned the Lecture #24, and the commit is e2443c3

I like to run tests in parallel, because it can detect race conditions. So I added t.Parallel() in all test functions and inside t.Run().
Then I run go test -race ./..., race detected, sample output like that:

=== CONT  TestGetAccountAPI/UnauthorizedUser
    testing.go:1319: race detected during execution of test
[GIN] 2022/09/15 - 11:32:26 | 500 |       532.9µs |                 | GET      "/accounts/972"
[GIN] 2022/09/15 - 11:32:26 | 404 |            0s |                 | GET      "/accounts/972"
    --- FAIL: TestGetAccountAPI/UnauthorizedUser (0.01s)

=== RUN   TestGetAccountAPI/NoAuthorization
=== PAUSE TestGetAccountAPI/NoAuthorization
=== CONT  TestGetAccountAPI/NoAuthorization
  github.com/techschool/simplebank/api.newTestServer()
      D:/Files/codes/clone/simplebank/api/main_test.go:20 +0xdb
  github.com/techschool/simplebank/api.TestGetAccountAPI.func19()
      D:/Files/codes/clone/simplebank/api/account_test.go:144 +0x40e
  testing.tRunner()
      E:/Programs/for_coding/Go/src/testing/testing.go:1446 +0x216
  testing.(*T).Run.func1()
      E:/Programs/for_coding/Go/src/testing/testing.go:1493 +0x47

Previous write at 0x00c0001d5668 by goroutine 43:
  github.com/go-playground/validator/v10.(*Validate).registerValidation()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:189 +0x116
  github.com/go-playground/validator/v10.(*Validate).RegisterValidationCtx()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:173 +0xa4
  github.com/go-playground/validator/v10.(*Validate).RegisterValidation()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:163 +0x46
  github.com/techschool/simplebank/api.NewServer()
      D:/Files/codes/clone/simplebank/api/server.go:36 +0x2e9
  github.com/techschool/simplebank/api.newTestServer()
      D:/Files/codes/clone/simplebank/api/main_test.go:20 +0xdb
  github.com/techschool/simplebank/api.TestGetAccountAPI.func19()
      D:/Files/codes/clone/simplebank/api/account_test.go:144 +0x40e
  testing.tRunner()
      E:/Programs/for_coding/Go/src/testing/testing.go:1446 +0x216
  testing.(*T).Run.func1()
      E:/Programs/for_coding/Go/src/testing/testing.go:1493 +0x47

Then I found v.RegisterValidation("currency", validCurrency) is not thead-safe, so I added a mutex in NewServer() function, but it still not working, a sample output like that:

=== CONT  TestGetAccountAPI
    testing.go:1319: race detected during execution of test
--- FAIL: TestGetAccountAPI (0.42s)
=== RUN   TestGetAccountAPI/OK
=== PAUSE TestGetAccountAPI/OK
=== CONT  TestGetAccountAPI/OK
  github.com/gin-gonic/gin.(*Context).Next()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 +0x14a
  github.com/gin-gonic/gin.RecoveryWithWriter.func1()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/recovery.go:83 +0xae
  github.com/gin-gonic/gin.(*Context).Next()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 +0x219
  github.com/gin-gonic/gin.LoggerWithConfig.func1()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/logger.go:241 +0x189
  github.com/gin-gonic/gin.(*Context).Next()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 +0xb63
  github.com/gin-gonic/gin.(*Engine).handleHTTPRequest()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:409 +0x75b
  github.com/gin-gonic/gin.(*Engine).ServeHTTP()
      D:/Files/codes/gopath/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:367 +0x367
  github.com/techschool/simplebank/api.TestCreateAccountAPI.func13()
      D:/Files/codes/clone/simplebank/api/account_test.go:273 +0x771
  testing.tRunner()
      E:/Programs/for_coding/Go/src/testing/testing.go:1446 +0x216
  testing.(*T).Run.func1()
      E:/Programs/for_coding/Go/src/testing/testing.go:1493 +0x47

Previous write at 0x00c0004140a8 by goroutine 62:
  github.com/go-playground/validator/v10.(*Validate).registerValidation()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:189 +0x116
  github.com/go-playground/validator/v10.(*Validate).RegisterValidationCtx()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:173 +0xa4
  github.com/go-playground/validator/v10.(*Validate).RegisterValidation()
      D:/Files/codes/gopath/pkg/mod/github.com/go-playground/validator/[email protected]/validator_instance.go:163 +0x46
    --- PASS: TestGetAccountAPI/OK (0.03s)

It seems that ctx.Next() in AuthMiddleware cause the error, so I locked the ctx.Next(), but the error still exists.

After several debugging, I found that if I delete t.Parallel() in user_test.go the error gone.

But the problem still not fixed, and I don't known why.

Could you please help me with that problem? The error is easy to reproduce, just make all tests run in parallel and run go test -race ./....

Thanks.

May I ask why an exec format error occurs during docker run?

I followed the video to learn how to build the image. When I ran the container, the following error occurred.

$ docker run --rm simplebank:latest
exec /app/start.sh: exec format error

My Dockerfile is as follows

# Build stage
FROM golang:1.21-alpine3.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o main main.go
RUN apk add curl
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz

# Run stage
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/main .
COPY --from=builder /app/migrate ./migrate
COPY app.env .
COPY start.sh .
COPY wait-for.sh .
COPY db/migration ./migration

EXPOSE 8000
CMD [ "/app/main" ]
ENTRYPOINT [ "/app/start.sh" ]

But when I comment out ENTRYPOINT, there are no errors and the debug log of the gin framework can be printed normally.

And when I use the following docker compose to run, this error will not be reported, and the debug log of the gin framework can be printed normally.

version: "3.9"
services:
  postgres:
    image: postgres:14-alpine
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=simple_bank
    ports:
      - "5432:5432"
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DB_SOURCE=postgresql://root:secret@postgres:5432/simple_bank?sslmode=disable
    depends_on:
      - postgres
    entrypoint:
      [
        "/app/wait-for.sh",
        "postgres:5432",
        "--",
        "/app/start.sh"
      ]
    command: [ "/app/main" ]

My start.sh is as follows, and also granted executable permissions

#/bin/sh
set -e

echo "run db migration"
source /app/app.env
/app/migrate -path /app/migration -database "$DB_SOURCE" -verbose up

echo "start the app"
exec "$@"

I don't know where the error is caused. Look forward to your reply.

PS: OS is centos8

$ docker version
Client: Docker Engine - Community
 Version:           24.0.2
 API version:       1.43
 Go version:        go1.20.4
 Git commit:        cb74dfc
 Built:             Thu May 25 21:53:10 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.2
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.4
  Git commit:       659604f
  Built:            Thu May 25 21:52:10 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.21
  GitCommit:        3dce8eb055cbb6872793272b4f20ed16117344f8
 runc:
  Version:          1.1.7
  GitCommit:        v1.1.7-0-g860f061
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Getting : BUG: slow write timer already active

This my storage.go file

package db

import (
	"context"
	"fmt"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

//Store provides all functions to execute db queries and transactions
type Store struct {
	*Queries
	db *pgx.Conn
}

type TransferTxParams struct {
	FromAccountID int64 					`json:"from_account_id"`
	ToAccountID 	int64 					`json:"to_account_id"`
	Amount 				pgtype.Numeric 	`json:"amount"`
}

type TransferTxResult struct {
	Transfer Transfer `json:"transfer"`
	FromAccount Account `json:"from_account"`
	ToAccount Account `json:"to_account"`
	FromEntry Entry `json:"from_entry"`
	ToEntry Entry `json:"to_entry"`
}

func NewStore(db *pgx.Conn) *Store {
		return &Store{
			db: db,
			Queries: New(db),
		}
}
//executeTranscation executres a function within a database transaction
func (store *Store) executeTransaction(ctx context.Context, fn func(*Queries) error) error {
	options := pgx.TxOptions {
		IsoLevel: pgx.TxIsoLevel(pgx.Deferrable),
		AccessMode: pgx.ReadWrite,
	}
	transaction, err := store.db.BeginTx(ctx, options)
	if err != nil {
		return err
	}
	query := New(transaction)
	err = fn(query)
	if err != nil {
		if rollbackError := transaction.Rollback(ctx); rollbackError != nil {
			return fmt.Errorf("transaction error: %v, rollback error: %v", err, rollbackError)
		}
		return err
	}
	return transaction.Commit(ctx)
}

func (store *Store) TransferTx(ctx context.Context, arg TransferTxParams) (TransferTxResult, error) {
	var result TransferTxResult
	err := store.executeTransaction(ctx, func(query *Queries) error {
		var err error

		transfer := CreateTransferParams(TransferTxParams {
			FromAccountID: 	arg.FromAccountID,
			ToAccountID: 		arg.ToAccountID,
			Amount: 				arg.Amount,
		})
		

		result.Transfer, err = query.CreateTransfer(ctx, transfer)
		if err != nil {
			return err
		}

		// result.FromEntry, err = query.CreateEntry(ctx, CreateEntryParams {
		// 	AccountID: arg.FromAccountID,
		// 	Amount: -arg.Amount.Int.Int64(),
		// })
		// if err != nil {
		// 	return err
		// }

		// result.ToEntry, err = query.CreateEntry(ctx, CreateEntryParams {
		// 	AccountID: arg.ToAccountID,
		// 	Amount: arg.Amount.Int.Int64(),
		// })
		// if err != nil {
		// 	return err
		// }

		// // TODO: update account's balance
		// account1, err := query.GetAccountForUpdate(ctx, arg.FromAccountID);
		// if err != nil {
		// 	return err
		// }

		// result.FromAccount, err = query.UpdateAccount(ctx, UpdateAccountParams {
		// 	ID: arg.FromAccountID,
		// 	Balance: util.FromIntToPgNumeric(account1.Balance.Int.Int64() - arg.Amount.Int.Int64()),
		// })
		// if err != nil {
		// 	return err
		// }

		// account2, err := query.GetAccountForUpdate(ctx, arg.ToAccountID);
		// if err != nil {
		// 	return err
		// }

		// result.ToAccount, err = query.UpdateAccount(ctx, UpdateAccountParams {
		// 	ID: arg.ToAccountID,
		// 	Balance: util.FromIntToPgNumeric(account2.Balance.Int.Int64() + arg.Amount.Int.Int64()),
		// })

		// if err != nil {
		// 	return err
		// }

		return nil
	})
	return result, err
}

And i am getting the following errors

>> before: 579 832
panic: BUG: slow write timer already active

goroutine 37 [running]:
github.com/jackc/pgx/v5/pgconn.(*PgConn).enterPotentialWriteReadDeadlock(...)
        /Users/bilalashraf/go/pkg/mod/github.com/jackc/pgx/[email protected]/pgconn/pgconn.go:1833
github.com/jackc/pgx/v5/pgconn.(*PgConn).flushWithPotentialWriteReadDeadlock(0x14000103680)
        /Users/bilalashraf/go/pkg/mod/github.com/jackc/pgx/[email protected]/pgconn/pgconn.go:1852 +0xb4
github.com/jackc/pgx/v5/pgconn.(*PgConn).Close(0x14000103680, {0x1009768a0?, 0x140000a8000})
        /Users/bilalashraf/go/pkg/mod/github.com/jackc/pgx/[email protected]/pgconn/pgconn.go:640 +0x160
github.com/jackc/pgx/v5.(*Conn).die(0x14000151c20, {0x1009767f8?, 0x100c9ef40?})
        /Users/bilalashraf/go/pkg/mod/github.com/jackc/pgx/[email protected]/conn.go:412 +0x74
github.com/jackc/pgx/v5.(*Conn).BeginTx(0x14000151c20, {0x1009767f8, 0x100c9ef40}, {{0x1007eca5c, 0xa}, {0x1007eca66, 0xa}, {0x0, 0x0}, {0x0, ...}})
        /Users/bilalashraf/go/pkg/mod/github.com/jackc/pgx/[email protected]/tx.go:104 +0x120
github.com/billalaashraf/simplebank/db/sqlc.(*Store).executeTransaction(0x0?, {0x1009767f8, 0x100c9ef40}, 0x140000b1960)
        /Users/bilalashraf/Work/Backend/simplebank/db/sqlc/storage.go:43 +0x80
github.com/billalaashraf/simplebank/db/sqlc.(*Store).TransferTx(_, {_, _}, {_, _, {_, _, _, _, _}})
        /Users/bilalashraf/Work/Backend/simplebank/db/sqlc/storage.go:60 +0xc8
github.com/billalaashraf/simplebank/db/sqlc.TestTransferTx.func1()
        /Users/bilalashraf/Work/Backend/simplebank/db/sqlc/storage_test.go:29 +0xa0
created by github.com/billalaashraf/simplebank/db/sqlc.TestTransferTx in goroutine 35
        /Users/bilalashraf/Work/Backend/simplebank/db/sqlc/storage_test.go:28 +0x2e0
FAIL    github.com/billalaashraf/simplebank/db/sqlc     0.698s
FAIL

What am i missing here.

Use httptest.NewRequest

Thanks for the great tutorial!

You currently use http.NewRequest in your tests:

request, err := http.NewRequest(...)
require.NoError(t, err)

This could be replaced with httptest.NewRequest which panics on error

request := httptest.NewRequest(...)

help:How can this SQL statement be changed to an SQLC statement?

if len(roleApis) > 0 {

		db := db.Orm.Debug().Model(&models.Api{}).
			Select("system_api.id").
			Joins("left join system_menu_api on system_menu_api.api = system_api.id")

		for _, p := range roleApis {
			db = db.Or("system_api.url = ? and system_api.method = ?", p[1], p[2])
		}
		err = db.Where("system_menu_api.menu = ?", menuId).Pluck("system_api.id", &apis).Error
		if err != nil {
			response.Error(c, err, response.GetApiError)
			return
		}
	}

Upgrade golang-jwt/jwt to v5

The dgrijalva/jwt-go is no longer maintained, and the development has been moved the a new repo golang-jwt/jwt.

However, the recent version of golang-jwt/jwt which is v5 has introduced a new breaking changes in the way Claims interface works.

While technically we can still use the v4, It would be very helpful if you can update this repo to support the v5 version.

Have an issue with list params not matching

   //creating random user. ///test function 
	user,_:= randomUser(t)
    n := 5
	//creating a slice of index n
	airlines := make([]db.Airline, n)
	for i:=0; i<n; i++ {
		 airlines[i] = RandomAirline(t)
	}
	//query
	type Query struct {
		pageID int
		pageSize int
	}
	test_cases := []struct{
		 name string
		 query  Query
		 setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
         buildStubs func(store *mockdb.MockStore) 
		 checkResponse func(recorder *httptest.ResponseRecorder)
		}{
			{
               name : "OK",
			   query: Query {
				   pageID: 1,
				   pageSize: n,
			   },
			   setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
				   addAuthorization(t, request, tokenMaker, authorizationBearerKey, user.Email, time.Minute)
			   },
			   buildStubs: func(store *mockdb.MockStore) {
				    arg := db.ListAirlinesParams{
						  Limit: int32(n),
						  Offset: 0,
					}
				    store.EXPECT().ListAirlines(gomock.Any(), gomock.Eq(arg)).Times(1).Return(airlines, nil)
			   },
			   checkResponse: func(recorder *httptest.ResponseRecorder) {
				    require.Equal(t, http.StatusOK, recorder.Code)
			   },
			},
		}



///live functionality

type listAirlinesRequest struct {
	 pageID int32 `form:"page_id" binding:"required,min=1"`
	 pageSize int32`form:"page_size" binding:"required,min=5,max=10"`
}

func (server *Server) GetAirlines(ctx *gin.Context){
		var req listAirlinesRequest
		if err := ctx.ShouldBindQuery(&req); err != nil{ 
				  ctx.JSON(http.StatusBadRequest, errorResponse(err))
				  return
		}
	    arg := db.ListAirlinesParams{
				  Limit: req.pageSize,
				  Offset: (req.pageID - 1) * req.pageSize,
	    }
		airlines, err := server.store.ListAirlines(ctx, arg)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, errorResponse(err))
			return
		}
		ctx.JSON(http.StatusOK, airlines)
  
  
  /////error
  --- FAIL: TestListAirlines (0.06s)
    --- FAIL: TestListAirlines/OK (0.00s)
        /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/airline.go:92: Unexpected call to *mockdb.MockStore.ListAirlines([0xc000124600 {0 0}]) at /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/airline.go:92 because: 
            expected call at /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/airline_test.go:142 doesn't match the argument at index 1.
            Got: {0 0} (db.ListAirlinesParams)
            Want: is equal to {5 0} (db.ListAirlinesParams)
        /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/controller.go:269: missing call(s) to *mockdb.MockStore.ListAirlines(is anything, is equal to {5 0} (db.ListAirlinesParams)) /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/airline_test.go:142
        /Applications/XAMPP/xamppfiles/htdocs/SPS-Web/api/controller.go:269: aborting test due to missing call(s)
FAIL
FAIL	github.com/thinkIt-africa/SPS-Web/api	1.250s. ```

Is it possible to bind `uri` and `json` for the same struct?

Eg. for updateAccount API, How to bind uri and json for the same struct?

type updateAccountRequest struct {
    ID int64 `uri:"id" binding:"required,min=1"`
    Currency string `json:"currency" binding:"required,currency"`
}

func (server *Server) updateArticle(ctx *gin.Context) {
    var req updateAccountRequest
    if err := ctx.ShouldBindUri(&req); err != nil {
        // send 400 Bad Request to the client
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    if err := ctx.ShouldBindJSON(&req); err != nil {
        // send 400 Bad Request to the client
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }
}

the code above does not work.

Does anyone know? Thanks in advance

sql.Open returns *sql.DB not DBTX

Hi,
Thanks for the great tutorial.

In the following code, New function expects DBTX. However, sql.open returns *sql.DB and error type therefore the code fails

	conn, err := sql.Open(dbDriver, dbSource)
	if err != nil {
		log.Fatal("cannot connect to db: ", err)
	}

	testQueries = New(conn) // <<--- type fails here 

The same issue will happen later

type Store struct {
	*Queries
	db *sql.DB
}

func NewStore(db *sql.DB) *Store {
	return &Store{
		db:      db,
		Queries: New(db), // <-- here
	}
}
cannot use db (variable of type *sql.DB) as DBTX value in argument to New: *sql.DB does not implement DBTX (wrong type for method Exec)
		have Exec(query string, args ...any) (sql.Result, error)
		want Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, 

Thanks!

Unit Test for SQLC is broken

Hello, firstly I would say thank you for the great tutorial in youtube.

I stumbled upon a problem where when I run make test, it return error like this:
image

The problem are, when we executing the command for make test, the test skipped func TestMain so the database connection doesn't created hence, this happen.

Temporary solution is, well, make new sql connection for each function. But as I read from github.com/stretchr/testify/suite, TestMain could be replaced by this.

Casbin

First, thank you for your course and giving me a lot of help!

  1. May I ask if you plan to support Casbin in the future

Add a license to the repository

Without a license, the default copyright laws apply, meaning that code owner retain all rights to source code and no one may reproduce, distribute, or create derivative works from work.

Open source licenses grant permission to use, modify, and redistribute licensed software for any purpose, subject to conditions preserving the provenance and openness of the software.

Regenerate sql.go files

Files in db/query have plural declaration of tables but when the sql.go files have singular structs of respective tables. Regenerate those files.

I encountered a problem when using sqlc

I run sqlc generate it encounter a error.
截屏2023-10-17 22 59 02
My CGO_ENABLED has long been set to 1, but sqlc still cannot run.
And i test my cgo ,it can run.
截屏2023-10-17 22 58 07
Here are the environment variables for my go and I use macos13.0
截屏2023-10-17 22 57 00
I've tried my best to fix it but still nothing works, hope someone can help me, I would be grateful

backend 18: May I ask why the unit test part of the user API fails?

When I re-study Lecture 18, the unit test was only partially passed. I would like to ask if I have configured something wrong? Looking forward to your reply

The error is as follows:

=== RUN   TestCreateUserAPI
=== RUN   TestCreateUserAPI/OK
[GIN] 2023/11/21 - 22:35:15 | 400 |      77.282µs |                 | POST     "/users"
    /home/test/github.com/test/simplebank_reappear/api/user_test.go:55:
        	Error Trace:	/home/test/github.com/test/simplebank_reappear/api/user_test.go:55
        	            				/home/test/github.com/test/simplebank_reappear/api/user_test.go:171
        	Error:      	Not equal:
        	            	expected: 200
        	            	actual  : 400
        	Test:       	TestCreateUserAPI
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: missing call(s) to *mockdb.MockStore.CreateUser(is anything, is equal to {yatlxo $2a$10$GlXhZH2JeqRG4ezTgC9uUeU9AEohAm4RY.TYGf5HeABoDvP.g.8/W slcpwu afqxbt@email} (db.CreateUserParams)) /home/test/github.com/test/simplebank_reappear/api/user_test.go:50
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: aborting test due to missing call(s)
=== RUN   TestCreateUserAPI/InternalError
[GIN] 2023/11/21 - 22:35:15 | 400 |      28.141µs |                 | POST     "/users"
    /home/test/github.com/test/simplebank_reappear/api/user_test.go:75:
        	Error Trace:	/home/test/github.com/test/simplebank_reappear/api/user_test.go:75
        	            				/home/test/github.com/test/simplebank_reappear/api/user_test.go:171
        	Error:      	Not equal:
        	            	expected: 500
        	            	actual  : 400
        	Test:       	TestCreateUserAPI
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: missing call(s) to *mockdb.MockStore.CreateUser(is anything, is anything) /home/test/github.com/test/simplebank_reappear/api/user_test.go:70
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: aborting test due to missing call(s)
=== RUN   TestCreateUserAPI/DuplicateUsername
[GIN] 2023/11/21 - 22:35:15 | 400 |      15.003µs |                 | POST     "/users"
    /home/test/github.com/test/simplebank_reappear/api/user_test.go:93:
        	Error Trace:	/home/test/github.com/test/simplebank_reappear/api/user_test.go:93
        	            				/home/test/github.com/test/simplebank_reappear/api/user_test.go:171
        	Error:      	Not equal:
        	            	expected: 403
        	            	actual  : 400
        	Test:       	TestCreateUserAPI
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: missing call(s) to *mockdb.MockStore.CreateUser(is anything, is anything) /home/test/github.com/test/simplebank_reappear/api/user_test.go:88
    /home/test/github.com/test/simplebank_reappear/api/controller.go:269: aborting test due to missing call(s)
=== RUN   TestCreateUserAPI/InvalidUsername
[GIN] 2023/11/21 - 22:35:15 | 400 |      16.933µs |                 | POST     "/users"
=== RUN   TestCreateUserAPI/InvalidEmail
[GIN] 2023/11/21 - 22:35:15 | 400 |      12.752µs |                 | POST     "/users"
=== RUN   TestCreateUserAPI/TooshortPassword
[GIN] 2023/11/21 - 22:35:15 | 400 |      14.628µs |                 | POST     "/users"
--- FAIL: TestCreateUserAPI (0.14s)
    --- FAIL: TestCreateUserAPI/OK (0.00s)
    --- FAIL: TestCreateUserAPI/InternalError (0.00s)
    --- FAIL: TestCreateUserAPI/DuplicateUsername (0.00s)
    --- PASS: TestCreateUserAPI/InvalidUsername (0.00s)
    --- PASS: TestCreateUserAPI/InvalidEmail (0.00s)
    --- PASS: TestCreateUserAPI/TooshortPassword (0.00s)
FAIL
FAIL	github.com/test/simplebank_reappear/api	0.143s

PS: I don’t have the file api/controller.go

The user_test.go is as follows

func TestCreateUserAPI(t *testing.T) {
	user, password := randomUser(t)
	hashedPassword, err := util.HashPassword(password)
	require.NoError(t, err)

	testCases := []struct {
		name          string
		body          gin.H
		buildStubs    func(store *mockdb.MockStore)
		checkResponse func(recorder *httptest.ResponseRecorder)
	}{
		{
			name: "OK",
			body: gin.H{
				"username":  user.Username,
				"password":  password,
				"full_name": user.FullName,
				"email":     user.Email,
			},
			buildStubs: func(store *mockdb.MockStore) {
				arg := db.CreateUserParams{
					Username:       user.Username,
					HashedPassword: hashedPassword,
					FullName:       user.FullName,
					Email:          user.Email,
				}

				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Eq(arg)).
					Times(1).
					Return(user, nil)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusOK, recorder.Code)
				fmt.Println("OK TEST", recorder.Body)
				requireBodyMatchUser(t, recorder.Body, user)
			},
		},
		{
			name: "InternalError",
			body: gin.H{
				"username":  user.Username,
				"password":  password,
				"full_name": user.FullName,
				"email":     user.Email,
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Any()).
					Times(1).
					Return(db.User{}, sql.ErrConnDone)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusInternalServerError, recorder.Code)
			},
		},
		{
			name: "DuplicateUsername",
			body: gin.H{
				"username":  user.Username,
				"password":  password,
				"full_name": user.FullName,
				"email":     user.Email,
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Any()).
					Times(1).
					Return(db.User{}, &pq.Error{Code: "23505"})
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusForbidden, recorder.Code)
			},
		},
		{
			name: "InvalidUsername",
			body: gin.H{
				"username":  "invalid-user#1",
				"password":  password,
				"full_name": user.FullName,
				"email":     user.Email,
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Any()).
					Times(0)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusBadRequest, recorder.Code)
			},
		},
		{
			name: "InvalidEmail",
			body: gin.H{
				"username":  user.Username,
				"password":  password,
				"full_name": user.FullName,
				"email":     "invalid-email",
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Any()).
					Times(0)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusBadRequest, recorder.Code)
			},
		},
		{
			name: "TooshortPassword",
			body: gin.H{
				"username":  user.Username,
				"password":  "123",
				"full_name": user.FullName,
				"email":     user.Email,
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					CreateUser(gomock.Any(), gomock.Any()).
					Times(0)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusBadRequest, recorder.Code)
			},
		},
	}

	for i := range testCases {
		tc := testCases[i]

		t.Run(tc.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			store := mockdb.NewMockStore(ctrl)
			tc.buildStubs(store)

			server := NewServer(store)
			recorder := httptest.NewRecorder()

			//Marshal body data to JSON
			data, err := json.Marshal(tc.body)
			require.NoError(t, err)

			url := "/users"
			request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
			require.NoError(t, err)

			server.router.ServeHTTP(recorder, request)
			tc.checkResponse(recorder)
		})
	}

}

func randomUser(t *testing.T) (user db.User, password string) {
	password = util.RandomString(6)
	hashedPassword, err := util.HashPassword(password)
	require.NoError(t, err)

	user = db.User{
		Username:       util.RandomOwner(),
		HashedPassword: hashedPassword,
		FullName:       util.RandomOwner(),
		Email:          util.RandomEmail(),
	}
	return
}

func requireBodyMatchUser(t *testing.T, body *bytes.Buffer, user db.User) {
	data, err := io.ReadAll(body)
	require.NoError(t, err)

	var gotUser db.User
	err = json.Unmarshal(data, &gotUser)

	require.NoError(t, err)
	require.Equal(t, user.Username, gotUser.Username)
	require.Equal(t, user.FullName, gotUser.FullName)
	require.Equal(t, user.Email, gotUser.Email)
	require.Empty(t, gotUser.HashedPassword)
}

The user.go is as follows

type createUserRequest struct {
	Username string `json:"username" binding:"required,alphanum"`
	Password string `json:"password" binding:"required,min=6"`
	FullName string `json:"full_name" binding:"required"`
	Email    string `json:"email" binding:"required,email"`
}

type createUserResponse struct {
	Username          string    `json:"username"`
	FullName          string    `json:"full_name"`
	Email             string    `json:"email"`
	PasswordChangedAt time.Time `json:"password_changed_at"`
	CreatedAt         time.Time `json:"created_at"`
}

func (server *Server) createUser(ctx *gin.Context) {
	var req createUserRequest
	if err := ctx.ShouldBindJSON(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, errorResponse(err))
		return
	}

	hashedPassword, err := util.HashPassword(req.Password)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, errorResponse(err))
		return
	}

	arg := db.CreateUserParams{
		Username:       req.Username,
		HashedPassword: hashedPassword,
		FullName:       req.FullName,
		Email:          req.Email,
	}
	user, err := server.store.CreateUser(ctx, arg)
	if err != nil {
		if pqErr, ok := err.(*pq.Error); ok {
			switch pqErr.Code.Name() {
			case "unique_violation":
				ctx.JSON(http.StatusForbidden, errorResponse(err))
				return
			}
		}
		ctx.JSON(http.StatusInternalServerError, errorResponse(err))
		return
	}

	resp := createUserResponse{
		Username:          user.Username,
		FullName:          user.FullName,
		Email:             user.Email,
		PasswordChangedAt: user.PasswordChangedAt,
		CreatedAt:         user.CreatedAt,
	}

	ctx.JSON(http.StatusOK, resp)
}

Edit docker-compose file

Thanks for this great class.

About lecture #25 (create docker-compose file), It is no longer used wait_for.sh for make order between services. and it isn't in docker document. It can implemented by condition and healthcheck simply. like below:

    `version: '3.9'
    services:
      postgres:
        image: postgres:14-alpine
        environment:
          - POSTGRES_USER=root
          - POSTGRES_PASSWORD=secret
          - POSTGRES_DB=simple_bank
        ports:
          - "5432:5432"
        volumes:
          - data-volume:/var/lib/postgresql/data
        healthcheck:
          test: "exit 0"
      redis:
        image: redis:7-alpine
      api:
        build:
          context: .
          dockerfile: Dockerfile
        ports:
          - "8080:8080"
          - "9090:9090"
        environment:
          - DB_SOURCE=postgresql://root:secret@postgres:5432/simple_bank?sslmode=disable
          - REDIS_ADDRESS=redis:6379
        depends_on:
          - postgres:
            condition: service_healthy
          - redis
    volumes:
      data-volume:`

Thanks again for this wonderful class.

add swagger

Would you like add a feature: use swagger to show apis?

Error in Docker Compose

Click me Hello everyone I am following the video [[Backend #25] How to write docker-compose file and control service start-up orders with wait-for.sh](https://www.youtube.com/watch?v=jf6sQsz0M1M&ab_channel=TECHSCHOOL), but I can't make it work.

Note: some components, such as, docker images and containers have different names from the course.

Thank you for your help!

Here are my files:

docker-compose-yml

version: "3.9"
services:
postgres:
image: postgres:latest
environment:
- POSTGRES_USER=root
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=simple_bank
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DB_SOURCE=postgresql://root:secret@postgres:5432/simple_bank?sslmode=disable
depends_on:
- postgres
entrypoint: ["/app/wait-for.sh", "postgres:5432", "--", "/app/start.sh"]
command: ["/app/main"]

Dockerfile

#Build Stage

FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main main.go

Run stage

FROM alpine
WORKDIR /app
COPY --from=builder /app/main .
COPY /app/migrate ./migrate
COPY app.env .
COPY start.sh .
COPY wait-for.sh .
COPY db/migrations ./migration

EXPOSE 8080
CMD [ "/app/main" ]
ENTRYPOINT [ "/app/start.sh" ]

start.sh

#!/bin/sh

set -e

echo "RUN DB migration"
/app/migrate -path /app/migration -database "$DB_SOURCE" -verbose up

echo "start the app"
exec ls -la
exec "$@"

Then I run docker compose down and delete my api image. However when I do docker compose up I get this error (in the picture), it might be something that I am not seeing... My files structure is in the second picture

image

image

Solved the issue, I was deleting the wrong image

i meet a problem

Hello master, I have discovered a vulnerability in github.com/techschool/simplebank/util when using go mod tidy. There is no released version in this file, which prevents me from continuing to use commands

Processing time zone

  1. Data obtained through PGSQL contains time fields that carry time zones
  2. Use gin.H to return the response

I get '2022-07-06T10:12:50.672517539Z', but I want '2022-07-06 10:12:50'

How to implement formatting in a generic way?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.