Slide 1

Slide 1 text

)PX8F#VJMU (P5FTUBCMF )551"1*4FSWFS (P$PO+1.BS "LJSB$IJLV

Slide 2

Slide 2 text

NF /BNF"LJSB$IJLV 5XJUUFS!@BDIJLV (JU)VC!BDIJLV (PPHMF4FBSDI"LJSB$IJLV'JSF

Slide 3

Slide 3 text

BHFOEB Ø  CBDLHSPVOE QSPEVDUTJ[FUFBN Ø  QSPKFDUCBTJDTTUSVDUVSF Ø  UFTUTXJUINPEFMQBDLBHF UFTUJOHXJUI3%#.4 Ø  UFTUTXJUITFSWJDFQBDLBHF UFTUJOHXJUIFYUFSOBM"1* Ø  UFTUTXJUIIUUQIBOEMFST Ø  MPDBMJOUFHSBUJPOUFTUTXJUIFYUFSOBMTFSWJDFT BOE XPSLFST

Slide 4

Slide 4 text

CBDLHSPVOE QSPEVDUTJ[FUFBN

Slide 5

Slide 5 text

CBDLHSPVOE QSPEVDU Ø  QSPEVDU Ø  "QQCBTFEJOTUBOU BOESFMPBEBCMFWJSUVBMQMBTUJD 7*4"QSFQBJEDBSEGPSFWFSZPOF Ø  J04 "OESPJE Ø  IUUQTWBOEMFKQ

Slide 6

Slide 6 text

CBDLHSPVOE TJ[F Ø  TJ[F BTPG.BS Ø  PGFOEQPJOUT JOUFSOBM"1* Ø  PGFYUFSOBMTFSWJDFT Ø  OPUJODMVEJOH4FOUSZ/FX3FMJD4UBUVT1BHFJP Ø  PGUBCMFT 1PTUHSF42- Ø  PGMJOFT (PDPEFXJUIPVUWFOEPS Ø  XJUIHFOFSBUFEDPEF Ø  XJUIPVUHFOFSBUFEDPEF

Slide 7

Slide 7 text

CBDLHSPVOE UFBN NFNCFS EFTJHO GSPOUFOE CBDLFOE *OGSB "84 JEFZVUB NPRBEB ZZPTIJLJ BDIJLV "TPG'FC

Slide 8

Slide 8 text

QSPKFDUCBTJDTTUSVDUVSF

Slide 9

Slide 9 text

QSPKFDUCBTJDT Ø  EFQFOEFODZNBOBHFNFOU Ø  IUUQTHJUIVCDPNNBUUOHPN Ø  VSMSPVUJOH Ø  IUUQTHJUIVCDPNHPSJMMBNVY Ø  +40/)ZQFS4DIFNB Ø  IUUQTHJUIVCDPNJOUFSBHFOUQSNE Ø  EBUBCBTFTDIFNBNBOBHFNFOUTDIFNBNJHSBUJPO Ø  IUUQTHJUIVCDPN[[[FFLTRMBMDIFNZ Ø  IUUQTHJUIVCDPN[[[FFLBMFNCJD Ø  UBCMFSPXEBUBHBUFXBZTUSVDUTGVODUJPOTHFOFSBUJPOVTJOH 1PTUHSF42-UBCMF Ø  IUUQTHJUIVCDPNBDIJLVEHX

Slide 10

Slide 10 text

QSPKFDUTUSVDUVSF WBOEMFBQJ NPEFM JOUFSBDUJPOXJUISECNT EBUBNPEFMJOUIFTFSWJDF OPUKVTUSECNTUBCMFSFQSFTFOUBUJPOT TFSWJDF EBUBNPEFMTJOUFSBDUXJUIFBDIPUIFS BMTP FYUFSOBM"1*TBSFDBMMFEJOTFSWJDFT JBQJ SFRVFTUSFTQPOTFTUSVDUT SFRVFTUWBMJEBUJPOGVODUJPOT IBOEMFST KPCT TFSWFS DPOG FUD FYTSWDMJFOU FYTSWDMJFOU DNE WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ FYTSWNPDLTFSWFS FYTSWNPDLTFSWFS ANPEFMAQLHGPS3%#.4 SFMBUFEJOUFSBDUJPOT ATFSWJDFAQLHGPSNPEFMT BOEFYU"1*JOUFSBDUJPOT AWBOEMFAQLHGPSIUUQ IBOEMFSTIUUQTFSWFS XPSLFSTBTZODKPCT 6TFADNEAUPDSFBUF EJFSFOUUZQFTPGCJOBSJFT

Slide 11

Slide 11 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 12

Slide 12 text

UFTUTXJUINPEFMQBDLBHF

Slide 13

Slide 13 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 14

Slide 14 text

42-"MDIFNZ"MFNCJDEHX Ø  42-"MDIFNZUPEFOF1PTUHSF42-UBCMFTJOEFYFTDPOTUSBJOUT Ø  "MFNCJDUPHFOFSBUFNJHSBUJPOTDSJQUT Ø  *UBMTPNBOBHFTVQHSBEFEPXOHSBEFEBUBCBTFPCKFDUT Ø  EHX HJUIVCDPNBDIJLVEHX Ø  *UHFOFSBUFT(PTUSVDUT BOETJNQMF5BCMF3PX%BUB(BUFXBZ GVODUJPOTGSPN1PTUHSF42-UBCMFNFUBEBUB Ø  8IZ Ø  #BDLFOEUFBN NPRBEB BOE* IBT1ZUIPOCBDLHSPVOE BOEVTFE UP42-"MDIFNZ Ø  8FBSFDPNGPSUBCMFXJUI42- Ø  (P03.03.JTIMJCSBSJFTTFFNBCJUTIBLZBUUIBUUJNF .BZ Ø  4JNJMBSBQQSPBDI Ø  42-#PJMFS HJUIVCDPNWBUUMFTRMCPJMFS Ø  YP HJUIVCDPNLORYP

Slide 15

Slide 15 text

42-"MDIFNZ"MFNCJDEHX class Todo(Base): __tablename__ = 'todo' uuid = Column(UUID, primary_key=True, server_default=text('uuid_generate_v1mc()')) user_id = Column(UUID, ForeignKey('todo_user.uuid'), nullable=False) name = Column(TEXT, nullable=False) duration = Column(BIGINT, nullable=False) started_at = Column(DateTime(timezone=True), nullable=False) is_completed = Column(Boolean, nullable=False) user = relationship('TodoUser', backref=backref('todos')) IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSECTDIFNBNPEFMTUPEPQZ

Slide 16

Slide 16 text

42-"MDIFNZ"MFNCJDEHX // Todo represents gotodoit_api.todo type Todo struct { UUID string // uuid UserID string // user_id Name string // name Duration int64 // duration StartedAt time.Time // started_at IsCompleted bool // is_completed } // Create inserts the Todo to the database. func (r *Todo) Create(db Queryer) error { err := db.QueryRow( `INSERT INTO todo (user_id, name, duration, started_at, is_completed) VALUES ($1, $2, $3, $ 4, $5) RETURNING uuid`, &r.UserID, &r.Name, &r.Duration, &r.StartedAt, &r.IsCompleted).Scan(&r.UUID) if err != nil { return errors.Wrap(err, "failed to insert todo") } return nil } // GetTodoByPk select the Todo from the database. func GetTodoByPk(db Queryer, pk0 string) (*Todo, error) { var r Todo err := db.QueryRow( `SELECT uuid, user_id, name, duration, started_at, is_completed FROM todo WHERE uuid = $1`, pk0).Scan(&r.UUID, &r.UserID, &r.Name, &r.Duration, &r.StartedAt, &r.IsCompleted) if err != nil { return nil, errors.Wrap(err, "failed to select todo") } return &r, nil } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMUBCMFHP

Slide 17

Slide 17 text

TFUVQUFBSEPXO Ø  %FOF5FTU$SFBUF4DIFNB 5FTU%SPQ4DIFNB BOE 5FTU$SFBUF5BCMFTJONPEFMUFTUJOHHP Ø  .BLFUIFTFGVODUJPOT1VCMJD5FTU"1* Ø  IUUQTTQFBLFSEFDLDPNNJUDIFMMIBEWBODFEUFTUJOH XJUIHP TMJEF Ø  8FDBOVTFUIFTFGVODUJPOTGSPNPUIFSQBDLBHFTXIJDIBSF EFQFOEVQPOEBUBCBTFUPTFUVQUFBSEPXO Ø  6TF5FTU.BJO NUFTUJOH. JONBJO@UFTUHP Ø  $SFBUFUFTUTDIFNB DSFBUFEBUBCBTFPCKFDUT SVOUFTUT BOE ESPQUFTUTDIFNBXJUIDBTDBEFPQUJPO Ø  8FVTFˎBMFNCJDVQHSBEFIFBETRMˏDPNNBOEUPDSFBUFBMM VQUPEBUFEBUBCBTFPCKFDUT

Slide 18

Slide 18 text

TFUVQUFBSEPXO func TestMain(m *testing.M) { flag.Parse() … testSchema := "gotodoit_api_test" testUser := "gotodoit_api_test" TestDropSchema(dbSetupCfg, testSchema) if err := TestCreateSchema(dbSetupCfg, testSchema, testUser); err != nil { log.Println(err) os.Exit(1) } if err := TestCreateTables(tblSetupCfg, ".."); err != nil { log.Println(err) os.Exit(1) } code := m.Run() if err := TestDropSchema(dbSetupCfg, testSchema); err != nil { log.Println(err) os.Exit(1) } os.Exit(code) } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMNBJO@UFTUHP

Slide 19

Slide 19 text

TJOHMFUSBOTBDUJPO42-ESJWFS Ø  (PPEUFTUTBSFSFQFBUBCMF BOEJOEFQFOEFOUPGFBDIPUIFSFWFO UIFZIBWFUPJOUFSBDUXJUIEBUBCBTF Ø  /PUTVDJFOUGPSHPPEUFTUT CVUOFDFTTBSZ Ø  4JOHMF5SBOTBDUJPO XIBU Ø  8IFOUIFDPOOFDUJPOJTPQFOFE JUTUBSUTBUSBOTBDUJPOBOE BMMPQFSBUJPOTQFSGPSNFEPOUIJTTRM%#XJMMCFXJUIJOUIBU USBOTBDUJPO Ø  IUUQTHJUIVCDPN%"5"%0(HPUYEC GPS.Z42- Ø  IUUQTHJUIVCDPNBDIJLVQHUYEC GPS1PTUHSF42- Ø  8FDBOUFTUSPMMCBDLJOUFTUUBSHFUGVODUJPOVTJOHQHUYEC TJODFJUˏTVTJOHTBWFQPJOUGFBUVSFGPSDPOO#FHJO BOE SPMMCBDLUPTBWFQPJOUXIFOUY3PMMCBDL JTDBMMFE

Slide 20

Slide 20 text

TJOHMFUSBOTBDUJPO42-ESJWFS // TestSetupTx create tx and cleanup func for test func TestSetupTx(t *testing.T) (Txer, func()) { db, err := sql.Open("txdb", "dummy") if err != nil { t.Fatal(err) } tx, err := db.Begin() if err != nil { t.Fatal(err) } cleanup := func() { tx.Rollback() db.Close() } return tx, cleanup } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMUFTUJOHHP func init() { txdb.Register("txdb", "postgres", "postgres://gotodoit_api_test@localhost:5432/gotodoit?sslmode =disable") }

Slide 21

Slide 21 text

TJOHMFUSBOTBDUJPO42-ESJWFS IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMVTFS@UFTUHP func TestUser_GetUserByAccessToken(t *testing.T) { tx, clean := TestSetupTx(t) defer clean() u := TestCreateUserData(t, tx, &TodoUser{}) at := TestCreateAccessTokenData(t, tx, u) u, found, err := GetUserByAccessToken(tx, at.Token) if err != nil { t.Fatal(err) } if !found { t.Error("must be found") } t.Logf("%v", u) }

Slide 22

Slide 22 text

QSFQBSJOHUFTUEBUB Ø  8IFODSFBUJOHUFTUEBUB Ø  )BTUPIBWFNFBOJOHGVMEFGBVMUWBMVFT BOECFFYQMJDJU XIFOVTJOHOPOEFGBVMUWBMVFT Ø  8FNFSHFUXP(PTUSVDUTUPDSFBUFUFTUEBUB Ø  TUSVDUTSFQSFTFOUJOH3%#.4UBCMFTBSFHFOFSBUFECZEXH BMMTUSVDUTIBWFUCM$SFBUF UY NFUIPEUPJOTFSUSFDPSEUP UBCMF Ø  0OFTUSVDUIBTBMMUIFEFGBVMUWBMVFT BOEUIFPUIFSIBTUFTU TQFDJDWBMVFT5IFMBUFSPWFSXSJUFTUIFGPSNFS Ø  IUUQTHJUIVCDPNBDIJLVPWX

Slide 23

Slide 23 text

QSFQBSJOHUFTUEBUB IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMUFTUJOH@UPEPHP // TestCreateUserData create user test data func TestCreateUserData(t *testing.T, tx Queryer, u *TodoUser) *TodoUser { uDefault := TodoUser{ Email: fmt.Sprintf( "akira.chiku.%[email protected]", hseq.Get("todo_user.email")), Username: fmt.Sprintf( "user%s", hseq.Get("todo_user.username")), Status: "active", } var target TodoUser if err := ovw.MergeOverwrite(uDefault, u, &target); err != nil { t.Fatal(err) } if err := target.Create(tx); err != nil { t.Fatal(err) } return &target }

Slide 24

Slide 24 text

IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNPEFMUPEP@UFTUHP func TestTodo_GetTodosByUserID(t *testing.T) { tx, clean := TestSetupTx(t) defer clean() u := TestCreateUserData(t, tx, &TodoUser{}) notCompleted := TestCreateTodoData(t, tx, u, &Todo{ Name: "buy milk", IsCompleted: false, }) completed := TestCreateTodoData(t, tx, u, &Todo{ Name: "done todo", IsCompleted: true, }) QSFQBSJOHUFTUEBUB

Slide 25

Slide 25 text

UFTUTXJUITFSWJDFQBDLBHF

Slide 26

Slide 26 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 27

Slide 27 text

"1*DMJFOUQBDLBHF Ø  5IFCFTUBSUJDMFPOIPXUPXSJUF(P"1*DMJFOUQBDLBHF Ø  IUUQEFFFFUDPNXSJUJOHHPBQJDMJFOU Ø  JUˏTJO+BQBOFTF CVU(PPHMFUSBOTMBUPSXJMMEPUIFKPCJ Ø  4ZNNFUSJD5FTUJOH PS(PMEFO'JMF XPSLTSFBMMZOJDFMZXIFO SFTQPOTFJTTUBUJD Ø  IUUQTTQFBLFSEFDLDPNNJUDIFMMIBEWBODFEUFTUJOH XJUIHP TMJEF Ø  IUUQTCMPHHPQIFSBDBEFNZDPNBEWFOUTZNNFUSJD BQJUFTUJOHJOHP Ø  *GSFTQPOTFIBTUPJODMVEFSFRVFTUWBMVFTUPGVMMZUFTU BOEJU BMTPSFRVJSFTUPUFTU)551TUBUVTDPEFIFBEFST *XPVMETVHHFTU UPDSFBUFGVOD XIUUQ3FTQPOTF8SJUFS SIUUQ3FRVFTU JOTJEF UIFUFTUGVODUJPO BOEVTFIUUQUFTUQBDLBHFUPTQJOVQJO NFNPSZUFTUTFSWFSUPUFTUBHBJOTU

Slide 28

Slide 28 text

func TestClient_EstimateTimeToCompleteSimple(t *testing.T) { req := &Task{ Name: "CS 101 week one homework", Difficulty: 2, } f := func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(ETC{ StatusCode: SuccessStatusCode, ConfidenceLevel: 10 * req.Difficulty, Time: 1000, }) return } ts := httptest.NewServer(http.HandlerFunc(f)) defer ts.Close() client := NewClient(TestNewConfig(ts.URL), &http.Client{}, nil) ctx := context.Background() res, err := client.EstimateTimeToComplete(ctx, req) if err != nil { t.Fatal(err) } if res.StatusCode != SuccessStatusCode { t.Errorf("want %d got %d", SuccessStatusCode, res.StatusCode) } } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSFTUDDMJFOU@UFTUHP

Slide 29

Slide 29 text

"1*DMJFOUQBDLBHF Ø  *UTFFNTPLBUUIFSTUHMBODF CVU Ø  "1*TFSWJDFVTVBMMZIBTNVMUJQMFFOEQPJOUT BOETPNF QSPDFTTFTOFFEUPVTFUIFNGPSPOFUBTL Ø  *UJTOPUGVOUPXSJUFGVOD IUUQ3FTQPOTF8SJUFS IUUQ3FRVFTU GPSFWFSZUFTU Ø  8FDBOXSJUFEFGBVMUCFIBWJPSTJO"1*DMJFOUQBDLBHF BOENBLF UIFNQVCMJDTPUIBUPUIFSQBDLBHFTEFQFOEPO"1*DMJFOU QBDLBHFDBOVTFUIFNGPSUFTUT

Slide 30

Slide 30 text

"1*DMJFOUQBDLBHF IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSFTUDDMJFOU@UFTUHP func TestClient_EstimateTimeToCompleteDefault(t *testing.T) { ts := httptest.NewServer(TestNewMux(DefaultHandlerMap)) defer ts.Close() client := NewClient(TestNewConfig(ts.URL), &http.Client{}, nil) ctx := context.Background() req := &Task{ Name: "CS 101 week one homework", Difficulty: 2, } res, err := client.EstimateTimeToComplete(ctx, req) if err != nil { t.Fatal(err) } if res.StatusCode != SuccessStatusCode { t.Errorf("want %d got %d", SuccessStatusCode, res.StatusCode) } }

Slide 31

Slide 31 text

"1*DMJFOUQBDLBHF func estimateHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(ETC{ StatusCode: SuccessStatusCode, ConfidenceLevel: 10, Time: 1000, }) return } // DefaultHandlerMap default url and handler map var DefaultHandlerMap = map[string]http.Handler{ "/api/v1/estimate-time": http.HandlerFunc(estimateHandler), } // TestNewMux creates mux for test/dev server func TestNewMux(hm map[string]http.Handler) *http.ServeMux { mux := http.NewServeMux() for url, handler := range hm { mux.Handle(url, handler) } return mux } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSFTUDUFTUJOHHP

Slide 32

Slide 32 text

"1*DMJFOUQBDLBHF Ø  8FIBEUPXSJUFDMJFOUQBDLBHFT TPFYUSBDUFEFTTFODFPGUIF QSFWJPVTQBHFUPBMJCSBSZ Ø  IUUQTHJUIVCDPNBDIJLVUFTUTWS

Slide 33

Slide 33 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 34

Slide 34 text

VTFNPEFMQVCMJDUFTU"1* Ø  6TF5FTU$SFBUF4DIFNB 5FTU%SPQ4DIFNB BOE 5FTU$SFBUF5BCMFTJONPEFMUFTUJOHHPUPTFUVQUFBSEPXO TDIFNB Ø  6TF5FTU$SFBUF5PEP%BUBJONPEFMUFTUJOH@UPEPHPUPDSFBUF UFTUEBUB

Slide 35

Slide 35 text

VTFDMJFOUQLHQVCMJDUFTU"1* Ø  6TF5FTU/FX.VYBOE%FGBVMU)BOEMFSEFOFEJO"1*DMJFOU QBDLBHFXJUIIUUQUFTU/FX4FSWFSUPDSFBUFUFTUTFSWFS Ø  0SZPVDBOEFOFZPVSPXOIBOEMFSTJOTJEFTFSWJDFQBDLBHF UFTUT

Slide 36

Slide 36 text

UFTUTXJUIIUUQIBOEMFST

Slide 37

Slide 37 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 38

Slide 38 text

BQQHMPCBMWBMVFT Ø  $SFBUF"QQTUSVDU BOEMFUJUIBWFBMMBQQMJDBUJPOHMPCBMWBMVFT Ø  %#JOUFSGBDF "QQ$POH "1*$MJFOUT Ø  5IJTNBLFTJUFBTZUPTFUVQEVNNZWBMVFT PSNPDLTJODF UIFTFBSFOPUQBDLBHFHMPCBM Ø  #FBXBSFUIBUUIFTFWBMVFTBSFBDDFTTFEDPODVSSFOUMZ Ø  TRM%# BOEIUUQ$MJFOUBSFDPODVSSFOUTBGB Ø  8FDBOBEENFUIPETUP"QQTUSVDUXIJDIIBOEMF)551SFRVFTU SFTQPOTF

Slide 39

Slide 39 text

BQQHMPCBMWBMVFT // App application type App struct { DB model.DBer Config *Config EstcClient *estc.Client } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSBQQHP // Healthcheck healthcheck func (app *App) Healthcheck( w http.ResponseWriter, r *http.Request) (int, interface{}, error) { m, err := service.Healthcheck(app.DB) if err != nil { c, e := iapi.NewInternalServerError() return c, e, errors.Wrap(err, "service.Healthcheck failed") } res := iapi.HealthcheckSelfResponse{ Message: m, } return http.StatusOK, res, nil } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSIBOEMFSHP

Slide 40

Slide 40 text

IUUQSFRVFTUSFTQPOTF Ø  "QQ)BOEMFS4FSWF)551 XIUUQ3FTQPOTF8SJUFS S IUUQ3FRVFTU Ø  IUUQ)BOEMFSDPNQBUJCMFNFUIPE Ø  UIJTJTXSBQQFSBSPVOEBDUVBMQSPDFTT Ø  FSSPSMPHHJOH SFTQPOTFTUSVDUUPKTPOFODPEF )551SFMBUFE UBTL FHSFEJSFDU JGOFDFTTBSZ Ø  "QQ)FBMUIDIFDL XIUUQ3FTQPOTF8SJUFS SIUUQ3FRVFTU JOU JOUFSGBDF\^ FSSPS Ø  5IJTJTOPUIUUQ)BOEMFSDPNQBUJCMFJUTFMG Ø  IPXZPVQSPDFTTSFRVFTU BOEDSFBUFSFTQPOTF Ø  UIJTSFUVSOWBMVFTNBLFTJUFBTZUPBTTFSUJOUFTUT

Slide 41

Slide 41 text

IUUQSFRVFTUSFTQPOTF // AppHandler internal type AppHandler struct { h func(w http.ResponseWriter, r *http.Request) (int, interface{}, error) } // ServeHTTP serve func (ah AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { logger := xlog.FromRequest(r) encoder := json.NewEncoder(w) reqInfo := xlog.F{"http_request": r} statusCode, res, err := ah.h(w, r) if err != nil { logger.Error(err, reqInfo) w.WriteHeader(statusCode) encoder.Encode(res) return } w.WriteHeader(statusCode) encoder.Encode(res) return } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSBQQHP

Slide 42

Slide 42 text

IUUQSFRVFTUSFTQPOTF // GetUserDetail get user func (app *App) GetUserDetail( w http.ResponseWriter, r *http.Request) (int, interface{}, error) { auth := getAuthData(r.Context()) u, err := service.GetUserByID(app.DB, auth.User.UUID) if err != nil { c, e := iapi.NewInternalServerError() return c, e, errors.Wrap(err, "service.GetUserByID failed") } res := iapi.UserSelfResponse{ ID: u.UUID, Username: u.Username, Email: u.Email, } return http.StatusOK, res, nil } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSBQQHP

Slide 43

Slide 43 text

IUUQSFRVFTUSFTQPOTF apiChain := jsonChain.Append( apiAuthMiddleware(app), ) router := mux.NewRouter() r := router.PathPrefix("/v1").Subrouter() // user r.Methods("POST").Path("/users").Handler( apiChain.Then(AppHandler{h: app.CreateUser})) r.Methods("POST").Path("/login").Handler( apiChain.Then(AppHandler{h: app.LoginUser})) r.Methods("GET").Path("/users/me").Handler( apiChain.Then(AppHandler{h: app.GetUserDetail})) IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSTFSWFSHP

Slide 44

Slide 44 text

IUUQSFRVFTUSFTQPOTF Ø  4FUVQ"QQGPSIBOEMFSUFTUT Ø  $SBUF"QQ$POHGPSUFTU Ø  $SFBUFTJOHMFUSBOTBDUJPOCZVTJOHNPEFM 5FTU4FUVQ%# UFTUJOH5 NPEFM%#FSUPTFUJUUP"QQ%# Ø  4FUNJEEMFXBSFHFOFSBUFEEBUB FHVTFSJE UPDPOUFYU$POUFYU

Slide 45

Slide 45 text

IUUQSFRVFTUSFTQPOTF func testSetupApp(t *testing.T) (*App, model.DBer, context.Context, func() ) { config, err := NewConfig("./conf/test.toml") if err != nil { t.Fatal(err) } db, cleanup := model.TestSetupDB(t) app := &App{ Config: config, DB: db, } ctx := context.Background() return app, db, ctx, func() { cleanup() } } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSNBJO@UFTUHP

Slide 46

Slide 46 text

IUUQSFRVFTUSFTQPOTF IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSIBOEMFS@UFTUHP func TestGetTodos(t *testing.T) { app, tx, ctx, cleanup := testSetupApp(t) defer cleanup() ... ts := httptest.NewServer(estc.TestNewMux(estc.DefaultHandlerMap)) defer ts.Close() app.EstcClient = estc.NewClient( estc.TestNewConfig(ts.URL), &http.Client{}, nil) req := testCreateRequest(t, "GET", "/v1/todos", nil).WithContext( context.WithValue( ctx, ctxKeyAuth, AuthModel{Token: "dummy", User: u}), ) wr := httptest.NewRecorder() status, res, err := app.GetTodos(wr, req) if err != nil { t.Fatal(err) }

Slide 47

Slide 47 text

MPDBMJOUFHSBUJPOUFTUT FYQFSJNFOUBM

Slide 48

Slide 48 text

QSPKFDUTUSVDUVSF WBOEMFTFSWFS WBOEMFXPSLFS WBOEMFDMJ WBOEMF TFSWJDF NPEFM TFSWJDF TFSWJDF &YUFSOBM "1* DMJFOU

Slide 49

Slide 49 text

NPDL"1*TFSWFS Ø  5PBDUVBMMZSFRVFTU BOESFDFJWFSFTQPOTFGSPNHPUPEPJUTFSWFS XFBMTPIBWFUPIBWFNPDL"1*TFSWFS FTUDTFSWFS&TUJNBUF 5JNFUP$PNQMFUF"1*TFSWJDF SVOOJOHUPQSPTFTSFRVFTUT Ø  4JODFXFEFOFE.VYJO"1*DMJFOUQBDLBHF XFDBOFBTJMZTUBSU )551TFSWFSJOTJEFDNEFTUDTFSWFSNBJOHP

Slide 50

Slide 50 text

NPDL"1*TFSWFS func main() { port := flag.String("p", "", "port number") flag.Parse() if *port != "" { s := estc.TestNewServer(estc.DefaultHandlerMap, *port) if err := s.ListenAndServe(); err != nil { log.Fatal(err) } } else { log.Fatal("service port number is not specified") } } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSDNEFTUDTFSWFSNBJOHP

Slide 51

Slide 51 text

NPDL"1*TFSWFS func estimateHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(ETC{ StatusCode: SuccessStatusCode, ConfidenceLevel: 10, Time: 1000, }) return } // DefaultHandlerMap default url and handler map var DefaultHandlerMap = map[string]http.Handler{ "/api/v1/estimate-time": http.HandlerFunc(estimateHandler), } // TestNewMux creates mux for test/dev server func TestNewMux(hm map[string]http.Handler) *http.ServeMux { mux := http.NewServeMux() for url, handler := range hm { mux.Handle(url, handler) } return mux } IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSFTUDUFTUJOHHP

Slide 52

Slide 52 text

UNVYQXCT Ø  4UBSUHPUPEPTFSWFS BOEFTUDTFSWFSJOPOFUNVYTFTTJPOVTJOH UNVYQ Ø  IUUQTHJUIVCDPNUPOZUNVYQ Ø  IUUQTHJUIVCDPNBDIJLVXCT Ø  "VUPSFMPBETFSWFSTPODPEFNPEJDBUJPOVTJOHXCT Ø  "GUFSTUBUJOHCPUITFSWFST XFDBOKVTUDVSMUPRVJDLDIFDLJGJUˏT XPSLJOH

Slide 53

Slide 53 text

UNVYQXCT session_name: gotodoit-dev-pane windows: - window_name: gotodoit-dev-services layout: tiled shell_command_before: - cd $GOPATH/src/github.com/achiku/gotodoit panes: - cd ./cmd/gotodo-server && gom exec wbs -c wbs.toml - cd ./cmd/estc-server && gom exec wbs -c wbs.toml IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSDNEUVNYTFTTJPOZNM

Slide 54

Slide 54 text

QZUFTUSFRVFTUT Ø  *UˏTEJDVMUUPTIBSFBMMDVSMDPNNBOETGPSFBDIFOEQPJOU BOE UFTUDBTF Ø  3BUIFSXSJUJOHTIFMMTDSJQUXJUICVODIPGDVSMDPNNBOET XF BSFVTJOHQZUFTUSFRVFTUT XFMPWF1ZUIPO BOENBJOUBJOJOH UIPTFTDSJQUTJOUIFSFQP

Slide 55

Slide 55 text

QZUFTUSFRVFTUT # -*- coding: utf-8 -*- import json import requests def test_todos(base_header, host): res = requests.get( host+'/v1/todos', headers=base_header) j = res.json() print(json.dumps(j, indent=2)) assert res.status_code == 200 IUUQTHJUIVCDPNBDIJLVHPUPEPJUCMPCNBTUFSECTDIFNBUFTUTUFTU@UPEPQZ

Slide 56

Slide 56 text

3FGFSFODFT Ø  "EESFGFSFODFTMBUFS˘