package transport import ( "context" "encoding/base64" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/config" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) type fakeAssetObjectStorage struct { content []byte gotKey string } func (s *fakeAssetObjectStorage) Validate() error { return nil } func (s *fakeAssetObjectStorage) PutBytes(context.Context, string, []byte, string) error { panic("unexpected call") } func (s *fakeAssetObjectStorage) GetBytes(_ context.Context, objectKey string) ([]byte, error) { s.gotKey = objectKey return s.content, nil } func (s *fakeAssetObjectStorage) Exists(context.Context, string) (bool, error) { panic("unexpected call") } func (s *fakeAssetObjectStorage) Delete(context.Context, string) error { panic("unexpected call") } func (s *fakeAssetObjectStorage) PublicURL(string) (string, error) { panic("unexpected call") } func TestAssetHandlerServeDesktop_AllowsStaleSignedTenantAsset(t *testing.T) { gin.SetMode(gin.TestMode) objectKey := "tenants/7/images/test.webp" storage := &fakeAssetObjectStorage{content: mustDecodeBase64(t, "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=")} handler := &AssetHandler{ app: testAssetApp(storage, "current-secret"), } staleToken := testSignedAssetToken(objectKey, "old-secret") router := gin.New() router.GET("/api/desktop/content/assets/:token", func(c *gin.Context) { ctx := WithDesktopClient(c.Request.Context(), &repository.DesktopClient{TenantID: 7}) c.Request = c.Request.WithContext(ctx) handler.ServeDesktop(c) }) resp := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/desktop/content/assets/"+staleToken+"?format=png", nil) router.ServeHTTP(resp, req) assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, objectKey, storage.gotKey) assert.Equal(t, "image/png", resp.Header().Get("Content-Type")) } func TestAssetHandlerServeDesktop_RejectsOtherTenantAsset(t *testing.T) { gin.SetMode(gin.TestMode) storage := &fakeAssetObjectStorage{content: []byte("not used")} handler := &AssetHandler{ app: testAssetApp(storage, "current-secret"), } token := testSignedAssetToken("tenants/8/images/test.webp", "old-secret") router := gin.New() router.GET("/api/desktop/content/assets/:token", func(c *gin.Context) { ctx := WithDesktopClient(c.Request.Context(), &repository.DesktopClient{TenantID: 7}) c.Request = c.Request.WithContext(ctx) handler.ServeDesktop(c) }) resp := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/desktop/content/assets/"+token, nil) router.ServeHTTP(resp, req) assert.Equal(t, http.StatusNotFound, resp.Code) assert.Empty(t, storage.gotKey) } func mustDecodeBase64(t *testing.T, value string) []byte { t.Helper() data, err := base64.StdEncoding.DecodeString(value) if err != nil { t.Fatalf("decode base64 fixture: %v", err) } return data } func testAssetApp(storage *fakeAssetObjectStorage, secret string) *bootstrap.App { store, err := config.NewStaticStore(&config.Config{JWT: config.JWTConfig{Secret: secret}}) if err != nil { panic(err) } return &bootstrap.App{ConfigStore: store, ObjectStorage: storage} } func testSignedAssetToken(objectKey, secret string) string { return (&AssetHandler{app: testAssetApp(nil, secret)}).signObjectKey(objectKey) }