vendredi 4 octobre 2019

Golang Client API Test Session Tracking

OK so I am making a Automated API Test Suite. I want to simulate actual end-user calls and do not want to use httptest.NewRecorder(). Everything is working fine except for the session (not authenticated from server) even though the first test run was testing the authenticate endpoint that only continues with a successful login (basic/Bearer) and session.

How do you track the session from one response to the next request? I really feel like i'm missing something small and stupid - but I am stuck! Included is full source as well as related output and server logs.

Oh, by the way - everything works fine in Postman. But if i export as Go Code and run I get same issue. Got to be something with tracking session.

Currently in the request setup i have:

req.Header.Add("Set-Cookie", Session)

the Session is updated in the response:

cookie := res.Header.Get("Set-Cookie")
if len(cookie) > 0 {
   Session = cookie
}

Any help would be much appreciated!

Current Output

=== RUN   TestAuthenticate

golang-api->TestAuthenticate: Testing endpoint /authenticate...
golang-api->TestAuthenticate: > No Authorization provided in header...

golang-api->TestAuthenticate: > Basic missing and bearer present in header...

golang-api->TestAuthenticate: > Bearer missing and basic present in header...

golang-api->TestAuthenticate: > Invalid Authorization Header (Bad Basic)...

golang-api->TestAuthenticate: > Invalid Authorization Header (Bad Bearer)...

golang-api->TestAuthenticate: > Invalid Authorization Header (Bad Basic & Bearer)...

golang-api->TestAuthenticate: > Valid Authorization Header (Good Basic & Bearer)...

golang-api->TestAuthenticate: SUCCESS Ok
--- PASS: TestAuthenticate (0.03s)
=== RUN   TestBonus

golang-api->TestBonus: Testing endpoint /bonus...
golang-api->TestBonus: > No Basic Authorization provided in header...

golang-api->TestBonus: > Invalid Basic Authorization Header (Bad Basic)...

golang-api->TestBonus: > Valid Basic Authorization Header (Good Basic)...

golang-api->TestBonus: ERROR Expected Status Code 200 got 401 instead
Exiting on GET request http://10.11.2.148:8080/bonus ...

REQUEST: &{Method:GET URL:http://10.11.2.148:8080/bonus Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[User-Agent:[GoLang API Test/0.1.0] Cache-Control:[no-cache] Authorization:[Basic YXNoaXNoOmEyNkIpbl5BPyU2V2YhQXNKdw==]] Body:<nil> GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:10.11.2.148:8080 Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil> Response:<nil> ctx:<nil>}

RESPONSE: &{Status:401 Unauthorized StatusCode:401 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[X-Xss-Protection:[1; mode=block] Referrer-Policy:[same-origin] Set-Cookie:[session=x4XJfYZsDpSqf_cWigHS3yuT5hqlEntU_WNfJW3j-661sGalr-NcvF6Ey1R340jmvjw8x6O1vie2ctHCW0C0hCjekfDESa3dxIKWt__7FSeidYg1VN-aaTljYeKHfQzuCPvjVak; Path=/; HttpOnly] Strict-Transport-Security:[max-age=31536000; includeSubDomains] X-Content-Type-Options:[nosniff] Content-Type:[application/json; charset=UTF-8] X-Frame-Options:[deny] Date:[Fri, 04 Oct 2019 11:29:01 GMT] Content-Length:[71]] Body:0xc000055100 ContentLength:71 TransferEncoding:[] Close:false Uncompressed:false Trailer:map[] Request:0xc0000e6c00 TLS:<nil>}

FAIL    command-line-arguments  0.038s

Server Logs

2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Invalid authorization, none provided","status":"Bad Request"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Invalid authorization, 1 provided, requires 2","status":"Bad Request"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Invalid authorization, 1 provided, requires 2","status":"Bad Request"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Issue decoding API Key: illegal base64 data at input byte 7","status":"Bad Request"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Invalid JWT timestamp: Expired","status":"Bad Request"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Issue decoding API Key: illegal base64 data at input byte 7","status":"Bad Request"}
2019/10/04 12:01:54 NOTIC: Auth   [4537722] via Redis cached value
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Invalid authorization, none provided","status":"Forbidden"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Issue decoding API Key: illegal base64 data at input byte 7","status":"Forbidden"}
2019/10/04 12:01:54  INFO: Handlers.HandleError(): Sent-> {"message":"Error: Session not authenticated","status":"Unauthorized"}

Full Source Provided to show what I am attempting in it's entirety. However it will obviously not work from your machine as the server is private.

package api


import (
    "time"
    "os"
    "fmt"
    "net/http"
    "testing"
)

// TODO: Create config loaded from yaml that allows external settings without recompile
// Make sure to update constants for your environment
const (
    UseHost             = "http://10.11.2.148:8080"
    ValidBasic          = "cWFfVGVzdDplaih4cVE+JyZzNS9QdThu"
    InvalidBasic        = "INVALID_BASIC"
    ValidBearer         = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.X3Jca1o8Db3AdypEu_yYCiOXmy00wgOxHS-3i2cC519UUupBKvYEx1V9x1OysrVWAmJSe7v0fs3iikq6L0Ky6zD6G75B4zmYqAbROFVCuwEKUWgVaykpJI-8u6NKkjGs5qNNxzAiHDQECv7AUUPhOuEeGpxq7y4OSMTDt4ZEYxml_5rNN-g057ViKZiBw9Q0J7FNKqWDf8XK852jcZi8VMM8dHaIv0pBP9M28lP6TaMmEqZRzIJbs2V4YxcMO5KkZhNySNeafycVAZyatiZKYyB0M7_qLSC7A1UEbK1U17n7u2FdE1JCdqKdxONvSFNzInJYwWcY9EU2qsACkRTULQ.pej62Nk9Ra_xVl5P.ZYzrJqx93_GSHgpznT2ZNq9lRWeIPd6r1bEnww0sXczsA84bYMrwLeN_Zb0nl2vvRFem32qUcWfkADVTvYl_3YgQ372darr7TW6geUEG8kQ72LcJ356iHRQ.KFzdokMLDhUlc8xkQ24uYQ"
    InvalidBearer       = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.ckkQI8TMs7kawGrI6ZA18zVk7NizpwF11aiU015lz2NM6s7gcN0OE0g2td5__TUIIjduHHKt8jCLaeyV0G_j_ai3-GMRLBEiMFizOUZqTUcPpwv0B-DQZ22B-exd-URtZL5RyxxT8FXoLnn7gMEXhURnj06Z0HbSuVIlryzQDwsfFsStw0CpxA1ZCEV3kC54xQetvBiptqJ1IDZaUh0fbAjFDURl9Q5tngyKvG8k6NZE8fpR3hbAukhuAU5cLP8P8DPeHY4wgSXN-xBJIG4vh2EAElHFDcBem_Hf6StkjPltMzUe1qbrLJOpmDNTgHqysCF7xvnJjkuYphGZa_W77g.WZvcU-KICyHg_OFu.LqQc7szbYzP8W3hsSRKWhJsKwFY_Vk38gO2y_-M_HdhgVATYzokKPladyEnOq5fuTegkd69TNwCb8ltC08n_gRLIwqdwgeJDIylWB4Lj_eTW.IJH0URIMFTnrSevMjxgt5Q"
)

type AuthorizationTest int
const (
    AuthorizationTestNone AuthorizationTest = 1 + iota      // No Authorization in header
    AuthorizationTestNoBasic                                // No Basic in Authorization
    AuthorizationTestNoBearer                               // No Bearer in Authorization
    AuthorizationTestInvalidBasic                           // Invalid Basic in Authorization
    AuthorizationTestInvalidBearer                          // Invalid Bearer in Authorization
    AuthorizationTestInvalidBasicBearer                     // Invalid Basic & Bearer in Authorization
    AuthorizationTestValid                                  // valid Basic & Bearer in Authorization
    AuthorizationTestBasicNone                              // Just Basic, none provided
    AuthorizationTestBasicInvalid                           // Just Basic, Invalid Provided
    AuthorizationTestBasicValid                             // Just Basic, Valid Provided
)

type LogType int
const (
    LogTypeEndpoint LogType = 1 + iota
    LogTypeSubEvent
    LogTypeError
    LogTypeSuccess
)

var (
    Session string
)

func Log(lt LogType, who string, what string) {
    if lt == LogTypeEndpoint {
        fmt.Printf("\n%s: Testing endpoint /%s...", who, what)
    } else if lt == LogTypeSubEvent {
        fmt.Printf("\n%s: > %s...", who, what)
    } else if lt == LogTypeError {
        fmt.Printf("\n%s: ERROR %s\n", who, what)
    } else if lt == LogTypeSuccess {
        fmt.Printf("\n%s: SUCCESS %s\n", who, what)
    } else {
        panic("Unknown Log Type")
    }
}

func addCookie(w http.ResponseWriter, name string, value string) {
    expire := time.Now().AddDate(0, 0, 1)
    cookie := http.Cookie{
        Name:    name,
        Value:   value,
        Expires: expire,
    }
    http.SetCookie(w, &cookie)
}

func doRequest(test, method string, url string, desiredStatus int, at AuthorizationTest) *http.Response {
    req, _ := http.NewRequest(method, url, nil)
    req.Header.Add("User-Agent", "GoLang API Test/0.1.0")
    req.Header.Add("Cache-Control", "no-cache")
    if len(Session) > 0 {
        //cookie, _ := req.Cookie("session")
        req.Header.Add("Set-Cookie", Session)
    }
    //req.Header.Add("Accept", "*/*")
    //req.Header.Add("Host", UseHost)
    //req.Header.Add("Accept-Encoding", "gzip, deflate")
    //req.Header.Add("Connection", "keep-alive")

    switch at {
    // Basic & Bearer
    case AuthorizationTestNone:
        break
    case AuthorizationTestNoBasic:
        req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ValidBearer))  
    case AuthorizationTestNoBearer:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s", ValidBasic))    
    case AuthorizationTestInvalidBasic:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s; Bearer %s", InvalidBasic, ValidBearer))  
    case AuthorizationTestInvalidBearer:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s; Bearer %s", ValidBasic, InvalidBearer))  
    case AuthorizationTestInvalidBasicBearer:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s; Bearer %s", InvalidBasic, InvalidBearer))    
    case AuthorizationTestValid:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s; Bearer %s", ValidBasic, ValidBearer))
    // Just Basic
    case AuthorizationTestBasicNone:
        break
    case AuthorizationTestBasicInvalid:
        req.Header.Add("Authorization", fmt.Sprintf("Basic %s", InvalidBasic))
    case AuthorizationTestBasicValid:
        req.Header.Add("Authorization", "Basic YXNoaXNoOmEyNkIpbl5BPyU2V2YhQXNKdw==")   
    }   

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()

    // Did we just try to authenticate?
    //if at == AuthorizationTestValid {
        // Was it successful?
        //if res.StatusCode == http.StatusOK {
            //Kewl, Save the Session for remaining tests
            //cookie := res.Header.Get("Set-Cookie")
            cookie, err := req.Cookie("session")
            if err != nil {
                //cookie, err := req.Cookie("session")
                Session = cookie.String()
                //req.Header.Add("Set-Cookie", cookie.String())
                //addCookie(res.Write,"session",cookie)
                //session=IOY2A55EKzTrqS1YLjWSq-axdDQAGLqLN5EyHdY5hd4sUTrVIPps3P1EhSP3pyFDFgIyW-EhpqzZeyVGsR_JyueVitAX6MWhpBFQiW2N8KDhedv2LviS_dhM0KGoMtTlRfYlQo-3cySIvA5oPOS_BSEuT2HmjlNCr2DzxDGX1Ig; Path=/; HttpOnly
                //Session = cookie
                fmt.Println(Session)
            }
        //} 
    //}

    if res.StatusCode != desiredStatus {
        Log(LogTypeError,test,fmt.Sprintf("Expected Status Code %d got %d instead",desiredStatus,res.StatusCode))
        fmt.Printf("Exiting on %s request %s ...\n", method, url)
        fmt.Printf("\nREQUEST: %+v\n",req)
        fmt.Printf("\nRESPONSE: %+v\n\n",res)
        os.Exit(1)
    }   

    return res
}

func doRequestAuthorization(caller string, method string, endpoint string) {

    url := fmt.Sprintf("%s/%s", UseHost, endpoint)
    if endpoint == "authenticate" {
        // User Basic & Bearer for Authorization
        Log(LogTypeSubEvent,caller,"No Authorization provided in header")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestNone)

        Log(LogTypeSubEvent,caller,"Basic missing and bearer present in header")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestNoBasic)

        Log(LogTypeSubEvent,caller,"Bearer missing and basic present in header")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestNoBearer)

        Log(LogTypeSubEvent,caller,"Invalid Authorization Header (Bad Basic)")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestInvalidBasic)

        Log(LogTypeSubEvent,caller,"Invalid Authorization Header (Bad Bearer)")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestInvalidBearer)

        Log(LogTypeSubEvent,caller,"Invalid Authorization Header (Bad Basic & Bearer)")
        _ = doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestInvalidBasicBearer)

        Log(LogTypeSubEvent,caller,"Valid Authorization Header (Good Basic & Bearer)")
        _ = doRequest(caller, method, url, http.StatusOK, AuthorizationTestValid)
    } else {
        // Use Basic only for Authorization
        Log(LogTypeSubEvent,caller,"No Basic Authorization provided in header")
        _ = doRequest(caller, method, url, http.StatusForbidden, AuthorizationTestBasicNone)

        Log(LogTypeSubEvent,caller,"Invalid Basic Authorization Header (Bad Basic)")
        _ = doRequest(caller, method, url, http.StatusForbidden, AuthorizationTestBasicInvalid)

        Log(LogTypeSubEvent,caller,"Valid Basic Authorization Header (Good Basic)")
        _ = doRequest(caller, method, url, http.StatusOK, AuthorizationTestBasicValid)
    }
}

// doRequestNoParameters tests endpoints that do not accept any parameters
func doRequestNoParameters(caller string, method string, endpoint string) *http.Response {
    Log(LogTypeSubEvent, caller, "No query parameters specified")
    url := fmt.Sprintf("%s/%s?foo=bar", UseHost, endpoint)
    return doRequest(caller, method, url, http.StatusBadRequest, AuthorizationTestInvalidBasicBearer)
}
func TestAuthenticate(t *testing.T) {
    caller := fmt.Sprintf("%s->%s", os.Getenv("REPOSITORY"), t.Name())
    Log(LogTypeEndpoint, caller, "authenticate")
    doRequestAuthorization(caller, "POST", "authenticate")
    Log(LogTypeSuccess, caller, "Ok")
}

// Test "GET", "/bonus"
func TestBonus(t *testing.T) {
    endpoint := "bonus"
    caller := fmt.Sprintf("%s->%s", os.Getenv("REPOSITORY"), t.Name())

    Log(LogTypeEndpoint, caller, endpoint)
    doRequestAuthorization(caller, "GET", endpoint)
    Log(LogTypeSubEvent,caller,"Valid Query")
    url := fmt.Sprintf("%s/%s", UseHost, endpoint)
    _ = doRequest(caller, "GET", url, http.StatusOK, AuthorizationTestBasicValid)
}

// TODO: Test "GET", "/category"
func TestCategory(t *testing.T) {
    endpoint := "category"
    caller := fmt.Sprintf("%s->%s", os.Getenv("REPOSITORY"), t.Name())

    Log(LogTypeEndpoint, caller, endpoint)
    doRequestAuthorization(caller, "GET", endpoint)
    Log(LogTypeSubEvent,caller,"Valid Query")
    url := fmt.Sprintf("%s/%s", UseHost, endpoint)
    _ = doRequest(caller, "GET", url, http.StatusOK, AuthorizationTestBasicValid)
}

Aucun commentaire:

Enregistrer un commentaire