Skip to content

Commit 4775b91

Browse files
Merge pull request #2398 from rajithacharith/env
Add env variables to export request
2 parents 9e48a0c + fa9f332 commit 4775b91

13 files changed

Lines changed: 948 additions & 781 deletions

File tree

api/WIP/export.yaml

Lines changed: 0 additions & 653 deletions
This file was deleted.

api/export.yaml

Lines changed: 517 additions & 0 deletions
Large diffs are not rendered by default.

backend/internal/system/export/handler.go

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"bytes"
2424
"fmt"
2525
"net/http"
26+
"strings"
2627

2728
serverconst "github.com/asgardeo/thunder/internal/system/constants"
2829
"github.com/asgardeo/thunder/internal/system/error/apierror"
@@ -42,7 +43,7 @@ func newExportHandler(service ExportServiceInterface) *exportHandler {
4243
}
4344
}
4445

45-
// HandleExportRequest handles the export request and returns YAML content.
46+
// HandleExportJSONRequest handles the export request and returns JSON with files.
4647
func (eh *exportHandler) HandleExportRequest(w http.ResponseWriter, r *http.Request) {
4748
logger := log.GetLogger().With(log.String(log.LoggerKeyComponentName, "ExportHandler"))
4849

@@ -60,52 +61,38 @@ func (eh *exportHandler) HandleExportRequest(w http.ResponseWriter, r *http.Requ
6061
// Export resources using the export service
6162
exportResponse, svcErr := eh.service.ExportResources(r.Context(), exportRequest)
6263
if svcErr != nil {
64+
if svcErr.Type == serviceerror.ServerErrorType {
65+
logger.Error("Error exporting resources", log.Any("serviceError", svcErr))
66+
}
6367
eh.handleError(w, svcErr)
6468
return
6569
}
6670

67-
// Combine all YAML files into a single response with separators
68-
var combinedYAML string
69-
for i, file := range exportResponse.Files {
70-
if i > 0 {
71-
combinedYAML += "\n---\n" // YAML document separator
72-
}
73-
combinedYAML += "# File: " + file.FileName + "\n"
74-
combinedYAML += file.Content
71+
jsonResponse := JSONExportResponse{
72+
Resources: buildCombinedResources(exportResponse.Files),
73+
EnvironmentVariables: "",
7574
}
76-
77-
// Return the combined YAML content
78-
w.Header().Set(serverconst.ContentTypeHeaderName, "application/yaml")
79-
w.WriteHeader(http.StatusOK)
80-
81-
if _, err := w.Write([]byte(combinedYAML)); err != nil {
82-
logger.Error("Error writing YAML response", log.Error(err))
83-
return
75+
if exportResponse.EnvFile != nil {
76+
jsonResponse.EnvironmentVariables = exportResponse.EnvFile.Content
8477
}
78+
79+
sysutils.WriteSuccessResponse(w, http.StatusOK, jsonResponse)
8580
}
8681

87-
// HandleExportJSONRequest handles the export request and returns JSON with files.
88-
func (eh *exportHandler) HandleExportJSONRequest(w http.ResponseWriter, r *http.Request) {
89-
exportRequest, err := sysutils.DecodeJSONBody[ExportRequest](r)
90-
if err != nil {
91-
errResp := apierror.ErrorResponse{
92-
Code: ErrorInvalidRequest.Code,
93-
Message: ErrorInvalidRequest.Error,
94-
Description: ErrorInvalidRequest.ErrorDescription,
95-
}
96-
sysutils.WriteErrorResponse(w, http.StatusBadRequest, errResp)
97-
return
98-
}
82+
func buildCombinedResources(files []ExportFile) string {
83+
var builder strings.Builder
9984

100-
// Export resources using the export service
101-
exportResponse, svcErr := eh.service.ExportResources(r.Context(), exportRequest)
102-
if svcErr != nil {
103-
eh.handleError(w, svcErr)
104-
return
85+
for i, file := range files {
86+
if i > 0 {
87+
builder.WriteString("\n---\n")
88+
}
89+
builder.WriteString("# File: ")
90+
builder.WriteString(file.FileName)
91+
builder.WriteString("\n")
92+
builder.WriteString(file.Content)
10593
}
10694

107-
// Return the JSON response with files
108-
sysutils.WriteSuccessResponse(w, http.StatusOK, exportResponse)
95+
return builder.String()
10996
}
11097

11198
// HandleExportZipRequest handles the export request and returns a ZIP file containing all resources.
@@ -126,6 +113,9 @@ func (eh *exportHandler) HandleExportZipRequest(w http.ResponseWriter, r *http.R
126113
// Export resources using the export service
127114
exportResponse, svcErr := eh.service.ExportResources(r.Context(), exportRequest)
128115
if svcErr != nil {
116+
if svcErr.Type == serviceerror.ServerErrorType {
117+
logger.Error("Error exporting resources", log.Any("serviceError", svcErr))
118+
}
129119
eh.handleError(w, svcErr)
130120
return
131121
}
@@ -170,6 +160,22 @@ func (eh *exportHandler) generateAndSendZipResponse(
170160
}
171161
}
172162

163+
if exportResponse.EnvFile != nil {
164+
envWriter, err := zipWriter.Create(exportResponse.EnvFile.FileName)
165+
if err != nil {
166+
logger.Error("Error creating env file in ZIP", log.String("fileName", exportResponse.EnvFile.FileName),
167+
log.Error(err))
168+
return fmt.Errorf("failed to create env file in ZIP: %w", err)
169+
}
170+
171+
if _, err = envWriter.Write([]byte(exportResponse.EnvFile.Content)); err != nil {
172+
logger.Error("Error writing env file content to ZIP",
173+
log.String("fileName", exportResponse.EnvFile.FileName),
174+
log.Error(err))
175+
return fmt.Errorf("failed to write env file content to ZIP: %w", err)
176+
}
177+
}
178+
173179
// Close the ZIP writer
174180
if err := zipWriter.Close(); err != nil {
175181
logger.Error("Error closing ZIP writer", log.Error(err))

backend/internal/system/export/handler_test.go

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ func (suite *HandlerTestSuite) TestGenerateAndSendZipResponse_Success() {
113113
Size: 42,
114114
},
115115
},
116+
EnvFile: &EnvironmentFile{
117+
FileName: ".env",
118+
Content: "TEST_APP_CLIENT_ID=\nTEST_APP_CLIENT_SECRET=\n",
119+
Size: 44,
120+
},
116121
Summary: &ExportSummary{
117122
TotalFiles: 2,
118123
TotalSize: 84,
@@ -142,7 +147,7 @@ func (suite *HandlerTestSuite) TestGenerateAndSendZipResponse_Success() {
142147
// Read and verify ZIP contents
143148
zipReader, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
144149
assert.NoError(suite.T(), err)
145-
assert.Len(suite.T(), zipReader.File, 2)
150+
assert.Len(suite.T(), zipReader.File, 3)
146151

147152
// Verify first file
148153
file1 := zipReader.File[0]
@@ -165,6 +170,17 @@ func (suite *HandlerTestSuite) TestGenerateAndSendZipResponse_Success() {
165170
assert.Equal(suite.T(), "name: test-app-2\ndescription: Test Application 2", string(content2))
166171
err = reader2.Close()
167172
assert.NoError(suite.T(), err)
173+
174+
// Verify env file
175+
envFile := zipReader.File[2]
176+
assert.Equal(suite.T(), ".env", envFile.Name)
177+
envReader, err := envFile.Open()
178+
assert.NoError(suite.T(), err)
179+
envContent, err := io.ReadAll(envReader)
180+
assert.NoError(suite.T(), err)
181+
assert.Equal(suite.T(), "TEST_APP_CLIENT_ID=\nTEST_APP_CLIENT_SECRET=\n", string(envContent))
182+
err = envReader.Close()
183+
assert.NoError(suite.T(), err)
168184
}
169185

170186
// Helper function to test ZIP response generation
@@ -425,7 +441,7 @@ func TestNewExportHandler(t *testing.T) {
425441

426442
// Handler Function Tests
427443

428-
// TestHandleExportRequest_Success tests successful YAML export.
444+
// TestHandleExportRequest_Success tests successful JSON export on the /export endpoint.
429445
func (suite *HandlerTestSuite) TestHandleExportRequest_Success() {
430446
// Setup mock expectations
431447
suite.mockAppService.EXPECT().GetApplication(mock.Anything, "app1").Return(&model.Application{
@@ -455,12 +471,14 @@ func (suite *HandlerTestSuite) TestHandleExportRequest_Success() {
455471

456472
// Assert response
457473
assert.Equal(suite.T(), http.StatusOK, w.Code)
458-
assert.Equal(suite.T(), "application/yaml", w.Header().Get("Content-Type"))
474+
assert.Equal(suite.T(), "application/json", w.Header().Get("Content-Type"))
459475

460-
responseBody := w.Body.String()
461-
assert.NotEmpty(suite.T(), responseBody)
462-
assert.Contains(suite.T(), responseBody, "# File:")
463-
assert.Contains(suite.T(), responseBody, "name: Test App 1")
476+
var response JSONExportResponse
477+
err := json.Unmarshal(w.Body.Bytes(), &response)
478+
assert.NoError(suite.T(), err)
479+
assert.Contains(suite.T(), response.Resources, "# File: Test_App_1.yaml")
480+
assert.Contains(suite.T(), response.Resources, "name: Test App 1")
481+
assert.Equal(suite.T(), "", response.EnvironmentVariables)
464482
}
465483

466484
// TestHandleExportRequest_InvalidJSON tests invalid JSON request handling.
@@ -505,8 +523,6 @@ func (suite *HandlerTestSuite) testServiceErrorResponse(
505523
switch endpoint {
506524
case "/export":
507525
suite.handler.HandleExportRequest(w, req)
508-
case "/export/json":
509-
suite.handler.HandleExportJSONRequest(w, req)
510526
case "/export/zip":
511527
suite.handler.HandleExportZipRequest(w, req)
512528
}
@@ -526,7 +542,7 @@ func (suite *HandlerTestSuite) TestHandleExportRequest_ServiceError() {
526542
suite.testServiceErrorResponse("POST", "/export", "app1", &ErrorNoResourcesFound, "EXP-1002")
527543
}
528544

529-
// TestHandleExportRequest_MultipleFiles tests YAML export with multiple files.
545+
// TestHandleExportRequest_MultipleFiles tests JSON export with multiple files.
530546
func (suite *HandlerTestSuite) TestHandleExportRequest_MultipleFiles() {
531547
// Setup mock expectations for multiple applications
532548
suite.mockAppService.EXPECT().GetApplication(mock.Anything, "app1").Return(&model.Application{
@@ -554,16 +570,17 @@ func (suite *HandlerTestSuite) TestHandleExportRequest_MultipleFiles() {
554570

555571
// Assert response
556572
assert.Equal(suite.T(), http.StatusOK, w.Code)
557-
responseBody := w.Body.String()
558-
559-
// Verify YAML separator between files
560-
assert.Contains(suite.T(), responseBody, "---")
561-
assert.Contains(suite.T(), responseBody, "name: App One")
562-
assert.Contains(suite.T(), responseBody, "name: App Two")
573+
assert.Equal(suite.T(), "application/json", w.Header().Get("Content-Type"))
563574

564-
// Count file headers
565-
fileHeaders := strings.Count(responseBody, "# File:")
566-
assert.Equal(suite.T(), 2, fileHeaders)
575+
var response JSONExportResponse
576+
err := json.Unmarshal(w.Body.Bytes(), &response)
577+
assert.NoError(suite.T(), err)
578+
assert.Contains(suite.T(), response.Resources, "# File: App_One.yaml")
579+
assert.Contains(suite.T(), response.Resources, "# File: App_Two.yaml")
580+
assert.Contains(suite.T(), response.Resources, "name: App One")
581+
assert.Contains(suite.T(), response.Resources, "name: App Two")
582+
assert.Contains(suite.T(), response.Resources, "---")
583+
assert.Equal(suite.T(), "", response.EnvironmentVariables)
567584
}
568585

569586
// TestHandleExportJSONRequest_Success tests successful JSON export.
@@ -585,37 +602,34 @@ func (suite *HandlerTestSuite) TestHandleExportJSONRequest_Success() {
585602
requestJSON, _ := json.Marshal(requestBody)
586603

587604
// Create HTTP request
588-
req := httptest.NewRequest("POST", "/export/json", bytes.NewReader(requestJSON))
605+
req := httptest.NewRequest("POST", "/export", bytes.NewReader(requestJSON))
589606
req.Header.Set("Content-Type", "application/json")
590607
w := httptest.NewRecorder()
591608

592609
// Execute
593-
suite.handler.HandleExportJSONRequest(w, req)
610+
suite.handler.HandleExportRequest(w, req)
594611

595612
// Assert response
596613
assert.Equal(suite.T(), http.StatusOK, w.Code)
597614
assert.Equal(suite.T(), "application/json", w.Header().Get("Content-Type"))
598615

599-
var response ExportResponse
616+
var response JSONExportResponse
600617
err := json.Unmarshal(w.Body.Bytes(), &response)
601618
assert.NoError(suite.T(), err)
602-
assert.Len(suite.T(), response.Files, 1)
603-
// Note: Service currently generates YAML files even with JSON format (fallback behavior)
604-
assert.Equal(suite.T(), "Test_App_JSON.yaml", response.Files[0].FileName)
605-
assert.Contains(suite.T(), response.Files[0].Content, "Test App JSON")
606-
assert.NotNil(suite.T(), response.Summary)
607-
assert.Equal(suite.T(), 1, response.Summary.TotalFiles)
619+
assert.Contains(suite.T(), response.Resources, "# File: Test_App_JSON.yaml")
620+
assert.Contains(suite.T(), response.Resources, "name: Test App JSON")
621+
assert.Equal(suite.T(), "", response.EnvironmentVariables)
608622
}
609623

610624
// TestHandleExportJSONRequest_InvalidJSON tests invalid JSON handling for JSON export.
611625
func (suite *HandlerTestSuite) TestHandleExportJSONRequest_InvalidJSON() {
612626
// Create malformed JSON request
613-
req := httptest.NewRequest("POST", "/export/json", strings.NewReader("invalid"))
627+
req := httptest.NewRequest("POST", "/export", strings.NewReader("invalid"))
614628
req.Header.Set("Content-Type", "application/json")
615629
w := httptest.NewRecorder()
616630

617631
// Execute
618-
suite.handler.HandleExportJSONRequest(w, req)
632+
suite.handler.HandleExportRequest(w, req)
619633

620634
// Assert error response
621635
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
@@ -630,7 +644,7 @@ func (suite *HandlerTestSuite) TestHandleExportJSONRequest_InvalidJSON() {
630644
// TestHandleExportJSONRequest_ServiceError tests service error handling for JSON export.
631645
func (suite *HandlerTestSuite) TestHandleExportJSONRequest_ServiceError() {
632646
// Setup mock to return service error
633-
suite.testServiceErrorResponse("POST", "/export/json", "app1", &serviceerror.InternalServerError, "EXP-1002")
647+
suite.testServiceErrorResponse("POST", "/export", "app1", &serviceerror.InternalServerError, "EXP-1002")
634648
}
635649

636650
// TestHandleExportZipRequest_Success tests successful ZIP export.
@@ -790,7 +804,7 @@ func (suite *HandlerTestSuite) TestHandleExportRequest_NilOptions() {
790804

791805
// Assert successful response with default behavior
792806
assert.Equal(suite.T(), http.StatusOK, w.Code)
793-
assert.Equal(suite.T(), "application/yaml", w.Header().Get("Content-Type"))
807+
assert.Equal(suite.T(), "application/json", w.Header().Get("Content-Type"))
794808
}
795809

796810
// TestHandleExportJSONRequest_EmptyFiles tests JSON export with no files.
@@ -802,12 +816,12 @@ func (suite *HandlerTestSuite) TestHandleExportJSONRequest_EmptyFiles() {
802816
requestJSON, _ := json.Marshal(requestBody)
803817

804818
// Create HTTP request
805-
req := httptest.NewRequest("POST", "/export/json", bytes.NewReader(requestJSON))
819+
req := httptest.NewRequest("POST", "/export", bytes.NewReader(requestJSON))
806820
req.Header.Set("Content-Type", "application/json")
807821
w := httptest.NewRecorder()
808822

809823
// Execute
810-
suite.handler.HandleExportJSONRequest(w, req)
824+
suite.handler.HandleExportRequest(w, req)
811825

812826
// Assert error response (empty applications list returns NoResourcesFound error)
813827
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
@@ -919,9 +933,9 @@ func BenchmarkHandleExportJSONRequest(b *testing.B) {
919933

920934
b.ResetTimer()
921935
for i := 0; i < b.N; i++ {
922-
req := httptest.NewRequest("POST", "/export/json", bytes.NewReader(requestJSON))
936+
req := httptest.NewRequest("POST", "/export", bytes.NewReader(requestJSON))
923937
req.Header.Set("Content-Type", "application/json")
924938
w := httptest.NewRecorder()
925-
handler.HandleExportJSONRequest(w, req)
939+
handler.HandleExportRequest(w, req)
926940
}
927941
}

backend/internal/system/export/init.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,10 @@ func registerRoutes(mux *http.ServeMux, exportHandler *exportHandler) {
5050
AllowCredentials: true,
5151
}
5252

53-
// YAML export endpoint - returns application/yaml
53+
// JSON export endpoint
5454
mux.HandleFunc(middleware.WithCORS("POST /export",
5555
exportHandler.HandleExportRequest, opts))
5656

57-
// JSON export endpoint - returns JSON with files (for backward compatibility)
58-
mux.HandleFunc(middleware.WithCORS("POST /export/json",
59-
exportHandler.HandleExportJSONRequest, opts))
60-
6157
// ZIP export endpoint - returns application/zip with individual files
6258
mux.HandleFunc(middleware.WithCORS("POST /export/zip",
6359
exportHandler.HandleExportZipRequest, opts))

backend/internal/system/export/init_test.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ func (suite *InitTestSuite) TestRegisterRoutes() {
139139
})
140140
}
141141

142-
// TestRegisterRoutes_YAMLEndpoint tests the YAML export endpoint registration
143-
func (suite *InitTestSuite) TestRegisterRoutes_YAMLEndpoint() {
142+
// TestRegisterRoutes_JSONEndpoint tests the JSON export endpoint registration.
143+
func (suite *InitTestSuite) TestRegisterRoutes_JSONEndpoint() {
144144
mux := http.NewServeMux()
145145
exporters := createTestExporters(suite.mockAppService, suite.mockIDPService,
146146
suite.mockNotificationService, suite.mockUserSchemaService)
@@ -161,27 +161,6 @@ func (suite *InitTestSuite) TestRegisterRoutes_YAMLEndpoint() {
161161
assert.NotEqual(suite.T(), http.StatusNotFound, w.Code)
162162
}
163163

164-
// TestRegisterRoutes_JSONEndpoint tests the JSON export endpoint registration
165-
func (suite *InitTestSuite) TestRegisterRoutes_JSONEndpoint() {
166-
mux := http.NewServeMux()
167-
exporters := createTestExporters(suite.mockAppService, suite.mockIDPService,
168-
suite.mockNotificationService, suite.mockUserSchemaService)
169-
mockService := newExportService(exporters, newParameterizer(templatingRules{}))
170-
exportHandler := newExportHandler(mockService)
171-
172-
registerRoutes(mux, exportHandler)
173-
174-
// Test POST /export/json endpoint
175-
req := httptest.NewRequest("POST", "/export/json", strings.NewReader(`{}`))
176-
req.Header.Set("Content-Type", "application/json")
177-
w := httptest.NewRecorder()
178-
179-
mux.ServeHTTP(w, req)
180-
181-
// Should not be 404 (route exists)
182-
assert.NotEqual(suite.T(), http.StatusNotFound, w.Code)
183-
}
184-
185164
// TestRegisterRoutes_ZIPEndpoint tests the ZIP export endpoint registration
186165
func (suite *InitTestSuite) TestRegisterRoutes_ZIPEndpoint() {
187166
mux := http.NewServeMux()
@@ -446,7 +425,6 @@ func TestRouteHandling_Standalone(t *testing.T) {
446425
expectNotFound bool
447426
}{
448427
{"POST", "/export", false},
449-
{"POST", "/export/json", false},
450428
{"POST", "/export/zip", false},
451429
{"OPTIONS", "/export", false},
452430
{"GET", "/export", true}, // Should be method not allowed, not not found

0 commit comments

Comments
 (0)