diff --git a/go-jwt/Dockerfile b/go-jwt/Dockerfile index 27c03f95..87108ef3 100644 --- a/go-jwt/Dockerfile +++ b/go-jwt/Dockerfile @@ -9,17 +9,17 @@ RUN go mod download # Copy the source code. Note the slash at the end, as explained in # https://docs.docker.com/reference/dockerfile/#copy -ADD https://keploy-enterprise.s3.us-west-2.amazonaws.com/releases/latest/assets/go_freeze_time_arm64 /lib/keploy/go_freeze_time_arm64 +ADD https://keploy-enterprise.s3.us-west-2.amazonaws.com/releases/latest/assets/go_freeze_time_amd64 /lib/keploy/go_freeze_time_amd64 #set suitable permissions -RUN chmod +x /lib/keploy/go_freeze_time_arm64 +RUN chmod +x /lib/keploy/go_freeze_time_amd64 # run the binary -RUN /lib/keploy/go_freeze_time_arm64 +RUN /lib/keploy/go_freeze_time_amd64 COPY *.go ./ # Build -RUN CGO_ENABLED=0 GOOS=linux go build -o /jwt-go +RUN CGO_ENABLED=0 GOOS=linux go build -tags=faketime -o /jwt-go EXPOSE 8000 diff --git a/go-jwt/README.md b/go-jwt/README.md index 86d27767..b1307f41 100644 --- a/go-jwt/README.md +++ b/go-jwt/README.md @@ -15,7 +15,7 @@ Install keploy via one-click:- curl --silent -O -L https://keploy.io/install.sh && source install.sh ``` -### Start the Postgres Database +### Start the MySQL Database ```zsh docker compose up -d db @@ -35,13 +35,13 @@ Once we have our binary file ready,this command will start the recording of API sudo -E keploy record -c "./go-jwt" ``` -Make API Calls using Hoppscotch, Postman or cURL command. Keploy with capture those calls to generate the test-suites containing testcases and data mocks. +Make API Calls using Hoppscotch, Postman or cURL command. Keploy will capture those calls to generate the test-suites containing testcases and data mocks. #### Generate testcases To genereate testcases we just need to make some API calls. You can use [Postman](https://www.postman.com/), [Hoppscotch](https://hoppscotch.io/), or simply `curl` -1. Generate shortned url +1. Check Health ```bash curl --request GET \ @@ -50,12 +50,12 @@ curl --request GET \ --header 'Host: localhost:8000' \ --header 'User-Agent: curl/7.81.0' ``` -this will return the response. -``` +This will return the response: +```json {"status": "healthy"} ``` -2. Fetch the Products +2. Generate a token ```bash curl --request GET \ --url http://localhost:8000/generate-token \ @@ -64,13 +64,13 @@ curl --request GET \ --header 'Accept: */*' ``` -we will get output: +You will get the following output: ```json {"token":""} ``` -3. Fetch a single product +3. Check the token ```sh curl --request GET \ @@ -80,16 +80,32 @@ curl --request GET \ --header 'User-Agent: curl/7.81.0' ``` -we will get output:- +You will get the following output: ```json {"username" : "example_user"} ``` -Now, since these API calls were captured as editable testcases and written to ``keploy/tests folder``. The keploy directory would also have `mocks` files that contains all the outputs. +4. Test the time-sensitive endpoint + +This sample includes a script `test_time_endpoint.sh` to easily test an endpoint that depends on the current time. + +First, make the script executable: +```sh +chmod +x test_time_endpoint.sh +``` + +Now, run the script. It will automatically use the current time for the API call. +```sh +./test_time_endpoint.sh +``` + +This will send a request to the `/check-time` endpoint and you should see a successful response: + +Now, since these API calls were captured as editable testcases and written to the `keploy/tests` folder. The keploy directory would also have `mocks` files that contain all the outputs. ![Testcase](./img/testcase.png?raw=true) -Now let's run the test mode (in the mux-sql directory, not the Keploy directory). +Now let's run the test mode (in the go-jwt directory, not the Keploy directory). ### Run captured testcases @@ -99,6 +115,4 @@ sudo -E keploy test -c "./go-jwt" --delay 10 Once done, you can see the Test Runs on the Keploy server, like this: -![Testrun](./img/testrun.png?raw=true) - - +![Testrun](./img/testrun.png?raw=true) \ No newline at end of file diff --git a/go-jwt/docker-compose.yaml b/go-jwt/docker-compose.yaml index 50c89151..bebc40e4 100644 --- a/go-jwt/docker-compose.yaml +++ b/go-jwt/docker-compose.yaml @@ -2,11 +2,13 @@ version: '3.8' services: db: - image: postgres - container_name: postgres + image: mysql:8.0 + container_name: mysql environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres + MYSQL_USER: myuser + MYSQL_PASSWORD: mypassword + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: mydb ports: - - "5432:5432" + - "3306:3306" + command: --default-authentication-plugin=mysql_native_password diff --git a/go-jwt/go.mod b/go-jwt/go.mod index 7c75bcac..65f5fe81 100644 --- a/go-jwt/go.mod +++ b/go-jwt/go.mod @@ -18,12 +18,12 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.1.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go-jwt/main.go b/go-jwt/main.go index 0447be9e..3fc79087 100644 --- a/go-jwt/main.go +++ b/go-jwt/main.go @@ -1,18 +1,19 @@ -// Package main is the entry point for the JWT-based user authentication service -// using Gin framework and PostgreSQL database. It provides endpoints for -// health check, token generation, and token validation. package main import ( + "fmt" "log" "net/http" "os" + "strconv" "time" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/postgres" + + // Change: Imported MySQL dialect instead of Postgres + _ "github.com/jinzhu/gorm/dialects/mysql" ) var ( @@ -36,12 +37,30 @@ type Claims struct { } func initDB() { - dsn := "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" - db, err = gorm.Open("postgres", dsn) - if err != nil { - log.Printf("Failed to connect to database: %s", err) + dsn := "myuser:mypassword@tcp(localhost:3306)/mydb?charset=utf8&parseTime=True&loc=Local&timeout=60s&readTimeout=60s" + + var connectionErr error + // Attempt to connect 10 times, waiting 2 seconds between attempts + for i := 0; i < 10; i++ { + db, connectionErr = gorm.Open("mysql", dsn) + if connectionErr == nil { + // Success! Check if we can actually ping + if err := db.DB().Ping(); err == nil { + break + } + } + + log.Printf("Database not ready yet (Attempt %d/10)... Waiting...", i+1) + time.Sleep(2 * time.Second) + } + + if connectionErr != nil { + log.Printf("Failed to connect to database after retries: %s", connectionErr) + log.Println("Ensure Docker is running and the MySQL container is ready.") os.Exit(1) } + + log.Println("Successfully connected to the database!") db.AutoMigrate(&User{}) } @@ -55,7 +74,7 @@ func GenerateTokenHandler(c *gin.Context) { // Normally, you'd get this from the request, but we're hardcoding it for simplicity username := "example_user" password := "example_password" - + fmt.Println("here is the current time :", time.Now().Unix()) // Set token expiration time expirationTime := time.Now().Add(5 * time.Minute) @@ -82,6 +101,7 @@ func GenerateTokenHandler(c *gin.Context) { if db.Where("username = ?", username).First(&user).RecordNotFound() { user = User{Username: username, Password: password, Token: tokenString} db.Create(&user) + fmt.Println("token getting saved :", user) } else { user.Password = password user.Token = tokenString @@ -135,8 +155,62 @@ func CheckTokenHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"username": claims.Username}) } +// CheckTimeHandler checks if a client-provided timestamp is within 1 second of the server time. +// The timestamp should be provided as a Unix timestamp in the 'ts' query parameter. +func CheckTimeHandler(c *gin.Context) { + // 1. Get the timestamp string from the URL query parameter 'ts' + clientTimeStr := c.Query("ts") + if clientTimeStr == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Missing 'ts' query parameter"}) + return + } + + // 2. Parse the string into an integer (Unix timestamp) + clientTimestamp, err := strconv.ParseInt(clientTimeStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid timestamp format. Must be a Unix timestamp in seconds."}) + return + } + + // 3. Convert the integer timestamp to a time.Time object + clientTime := time.Unix(clientTimestamp, 0) + serverTime := time.Now() + + // 4. Calculate the duration (difference) between server time and client time + diff := serverTime.Sub(clientTime) + + // 5. Get the absolute value of the duration, since the client could be ahead or behind + if diff < 0 { + diff = -diff + } + + log.Printf( + "Server Time: %s", + serverTime.String(), + ) + + log.Printf( + "Time difference: %s", + diff.String(), + ) + + // 6. Check if the difference is greater than 1 second + if diff > time.Second { + c.Status(http.StatusBadRequest) + return + } + + time.Sleep(1 * time.Second) + + // 7. If the check passes, send a 200 OK response + c.Status(http.StatusOK) +} + func main() { + // Give Docker a moment to spin up if running via compose, + // though strictly 2 seconds might not be enough for a cold MySQL boot. time.Sleep(2 * time.Second) + initDB() defer func() { if err := db.Close(); err != nil { @@ -150,6 +224,7 @@ func main() { router.GET("/health", HealthCheckHandler) router.GET("/generate-token", GenerateTokenHandler) router.GET("/check-token", CheckTokenHandler) + router.GET("/check-time", CheckTimeHandler) err = router.Run(":8000") if err != nil && err != http.ErrServerClosed { diff --git a/go-jwt/test_time_endpoint.sh b/go-jwt/test_time_endpoint.sh new file mode 100755 index 00000000..d7a491ee --- /dev/null +++ b/go-jwt/test_time_endpoint.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script sends a request to the /check-time endpoint. +# - If run without arguments, it uses the current Unix timestamp (should succeed). +# - If run with a number as an argument, it uses that number as the timestamp. + +# --- Configuration --- +HOSTNAME="localhost" +PORT="8000" +ENDPOINT="/check-time" +# --------------------- + +# Check if a command-line argument (a custom timestamp) was provided +if [ -n "$1" ]; then + # Use the provided argument as the timestamp + TIMESTAMP_TO_SEND="$1" + echo "Using provided timestamp: $TIMESTAMP_TO_SEND" +else + # No argument provided, get the current Unix timestamp + TIMESTAMP_TO_SEND=$(date +%s) + echo "Using current timestamp: $TIMESTAMP_TO_SEND" +fi + +# Construct the full URL +URL="http://${HOSTNAME}:${PORT}${ENDPOINT}?ts=${TIMESTAMP_TO_SEND}" + +# Send the request using curl and print the result +echo "Sending request to: ${URL}" +curl -s "${URL}" # The -s flag makes curl silent (no progress meter) +echo # Add a newline for cleaner terminal output