Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions go-jwt/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
42 changes: 28 additions & 14 deletions go-jwt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 \
Expand All @@ -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 \
Expand All @@ -64,13 +64,13 @@ curl --request GET \
--header 'Accept: */*'
```

we will get output:
You will get the following output:

```json
{"token":"<your_jwt_token>"}
```

3. Fetch a single product
3. Check the token

```sh
curl --request GET \
Expand All @@ -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

Expand All @@ -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)
14 changes: 8 additions & 6 deletions go-jwt/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion go-jwt/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 84 additions & 9 deletions go-jwt/main.go
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 1 in go-jwt/main.go

View workflow job for this annotation

GitHub Actions / lint (go-jwt)

package-comments: should have a package comment (revive)

Check warning on line 1 in go-jwt/main.go

View workflow job for this annotation

GitHub Actions / lint (go-jwt)

package-comments: should have a package comment (revive)

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 (
Expand All @@ -36,12 +37,30 @@
}

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{})
}

Expand All @@ -55,7 +74,7 @@
// 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)

Expand All @@ -82,6 +101,7 @@
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
Expand Down Expand Up @@ -135,8 +155,62 @@
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)
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When rejecting requests due to time difference, the response body is empty. Consider providing an error message explaining why the request was rejected, similar to other error cases in this handler.

Suggested change
c.Status(http.StatusBadRequest)
c.JSON(http.StatusBadRequest, gin.H{"error": "Time difference between client and server is too large (must be <= 1 second)"})

Copilot uses AI. Check for mistakes.
return
}

time.Sleep(1 * time.Second)

Comment on lines +203 to +204
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 1-second sleep artificially delays all valid time check requests, which will significantly impact API response times and throughput. Consider removing this sleep or making it configurable for testing purposes only.

Suggested change
time.Sleep(1 * time.Second)

Copilot uses AI. Check for mistakes.
// 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 {
Expand All @@ -150,6 +224,7 @@
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 {
Expand Down
30 changes: 30 additions & 0 deletions go-jwt/test_time_endpoint.sh
Original file line number Diff line number Diff line change
@@ -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
Loading