initial core commit

This commit is contained in:
2025-12-28 13:24:16 +01:00
commit 0e8d108156
12 changed files with 519 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
package handler
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// Health returns basic service health
func Health(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
// Hello returns a simple greeting
func Hello(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hello from lambda-iot core"})
}
// Login issues a JWT token for demo purposes
func Login(c *gin.Context) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.BindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid json"})
return
}
// demo credentials
if req.Username != "admin" || req.Password != "password" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
// create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": req.Username,
"exp": time.Now().Add(time.Hour).Unix(),
})
secret := "secret"
if s := c.GetHeader("X-JWT-SECRET"); s != "" {
secret = s
}
signed, err := token.SignedString([]byte(secret))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not sign token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": signed})
}
// Protected requires a valid JWT and returns the token claims
func Protected(c *gin.Context) {
if v, ok := c.Get("claims"); ok {
c.JSON(http.StatusOK, gin.H{"claims": v})
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "no claims"})
}
+87
View File
@@ -0,0 +1,87 @@
package handler
import (
"encoding/json"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func TestHealth(t *testing.T) {
r := gin.New()
r.GET("/health", Health)
req := httptest.NewRequest("GET", "/health", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("expected 200 got %d", w.Code)
}
var body map[string]string
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode: %v", err)
}
if body["status"] != "ok" {
t.Fatalf("unexpected body: %#v", body)
}
}
func TestHello(t *testing.T) {
r := gin.New()
r.GET("/hello", Hello)
req := httptest.NewRequest("GET", "/hello", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("expected 200 got %d", w.Code)
}
var body map[string]string
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode: %v", err)
}
if body["message"] == "" {
t.Fatalf("unexpected body: %#v", body)
}
}
func TestLoginAndProtected(t *testing.T) {
secret := "testsecret"
// create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "tester",
"exp": time.Now().Add(time.Hour).Unix(),
})
signed, err := token.SignedString([]byte(secret))
if err != nil {
t.Fatalf("sign token: %v", err)
}
// test protected route
r := gin.New()
// use middleware inline for test
r.Use(func(c *gin.Context) {
c.Request.Header.Set("Authorization", "Bearer "+signed)
})
r.GET("/protected", func(c *gin.Context) {
c.Set("claims", jwt.MapClaims{"sub": "tester"})
Protected(c)
})
req := httptest.NewRequest("GET", "/protected", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("expected 200 got %d", w.Code)
}
var body map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode: %v", err)
}
if body["claims"] == nil {
t.Fatalf("expected claims in response")
}
}