@@ -28,6 +28,7 @@ package main
2828
2929import (
3030 "bytes"
31+ "database/sql"
3132 "encoding/base64"
3233 "encoding/json"
3334 "fmt"
@@ -40,13 +41,25 @@ import (
4041 "testing"
4142 "time"
4243
44+ "github.com/doug-martin/goqu/v9"
4345 "github.com/eclipse-basyx/basyx-go-components/internal/common/testenv"
4446 _ "github.com/lib/pq" // PostgreSQL Treiber
4547 "github.com/stretchr/testify/assert"
4648 "github.com/stretchr/testify/require"
4749)
4850
4951const actionDeleteAllAAS = "DELETE_ALL_AAS"
52+ const defaultIntegrationTestDSN = "postgres://admin:admin123@127.0.0.1:6432/basyxTestDB?sslmode=disable"
53+
54+ var integrationTestDSN = getIntegrationTestDSN ()
55+
56+ func getIntegrationTestDSN () string {
57+ if dsn := os .Getenv ("AASREPOSITORY_INTEGRATION_TEST_DSN" ); dsn != "" {
58+ return dsn
59+ }
60+
61+ return defaultIntegrationTestDSN
62+ }
5063
5164func deleteAllAAS (t * testing.T , runner * testenv.JSONSuiteRunner , stepNumber int ) {
5265 for {
@@ -165,6 +178,103 @@ func downloadThumbnail(endpoint string) ([]byte, string, int, error) {
165178 return body , resp .Header .Get ("Content-Type" ), resp .StatusCode , nil
166179}
167180
181+ func getJSONResponse (endpoint string ) (map [string ]any , int , error ) {
182+ req , err := http .NewRequest (http .MethodGet , endpoint , nil )
183+ if err != nil {
184+ return nil , 0 , fmt .Errorf ("failed to create request: %v" , err )
185+ }
186+
187+ client := & http.Client {Timeout : 10 * time .Second }
188+ resp , err := client .Do (req )
189+ if err != nil {
190+ return nil , 0 , fmt .Errorf ("failed to send request: %v" , err )
191+ }
192+ defer func () { _ = resp .Body .Close () }()
193+
194+ body , err := io .ReadAll (resp .Body )
195+ if err != nil {
196+ return nil , resp .StatusCode , fmt .Errorf ("failed to read response: %v" , err )
197+ }
198+
199+ var payload map [string ]any
200+ if err = json .Unmarshal (body , & payload ); err != nil {
201+ return nil , resp .StatusCode , fmt .Errorf ("failed to unmarshal response: %v" , err )
202+ }
203+
204+ return payload , resp .StatusCode , nil
205+ }
206+
207+ func getThumbnailWithoutFollowingRedirect (endpoint string ) (int , string , error ) {
208+ req , err := http .NewRequest (http .MethodGet , endpoint , nil )
209+ if err != nil {
210+ return 0 , "" , fmt .Errorf ("failed to create request: %v" , err )
211+ }
212+
213+ client := & http.Client {
214+ Timeout : 10 * time .Second ,
215+ CheckRedirect : func (_ * http.Request , _ []* http.Request ) error {
216+ return http .ErrUseLastResponse
217+ },
218+ }
219+
220+ resp , err := client .Do (req )
221+ if err != nil {
222+ return 0 , "" , fmt .Errorf ("failed to send request: %v" , err )
223+ }
224+ defer func () { _ = resp .Body .Close () }()
225+
226+ return resp .StatusCode , resp .Header .Get ("Location" ), nil
227+ }
228+
229+ func setExternalThumbnailForAAS (aasID string , externalURL string ) error {
230+ db , err := sql .Open ("postgres" , integrationTestDSN )
231+ if err != nil {
232+ return fmt .Errorf ("failed to open db connection: %v" , err )
233+ }
234+ defer func () { _ = db .Close () }()
235+
236+ dialect := goqu .Dialect ("postgres" )
237+
238+ selectAASDBIDSQL , selectAASDBIDArgs , selectAASDBIDBuildErr := dialect .
239+ From (goqu .T ("aas" )).
240+ Select (goqu .I ("id" )).
241+ Where (goqu .I ("aas_id" ).Eq (aasID )).
242+ Limit (1 ).
243+ ToSQL ()
244+ if selectAASDBIDBuildErr != nil {
245+ return fmt .Errorf ("failed to build aas id query: %v" , selectAASDBIDBuildErr )
246+ }
247+
248+ var aasDBID int64
249+ if queryErr := db .QueryRow (selectAASDBIDSQL , selectAASDBIDArgs ... ).Scan (& aasDBID ); queryErr != nil {
250+ return fmt .Errorf ("failed to query aas db id: %v" , queryErr )
251+ }
252+
253+ upsertThumbnailSQL , upsertThumbnailArgs , upsertThumbnailBuildErr := dialect .
254+ Insert (goqu .T ("thumbnail_file_element" )).
255+ Rows (goqu.Record {
256+ "id" : aasDBID ,
257+ "content_type" : "application/octet-stream" ,
258+ "file_name" : "external-thumbnail" ,
259+ "value" : externalURL ,
260+ }).
261+ OnConflict (goqu .DoUpdate ("id" , goqu.Record {
262+ "content_type" : "application/octet-stream" ,
263+ "file_name" : "external-thumbnail" ,
264+ "value" : externalURL ,
265+ })).
266+ ToSQL ()
267+ if upsertThumbnailBuildErr != nil {
268+ return fmt .Errorf ("failed to build thumbnail upsert query: %v" , upsertThumbnailBuildErr )
269+ }
270+
271+ if _ , execErr := db .Exec (upsertThumbnailSQL , upsertThumbnailArgs ... ); execErr != nil {
272+ return fmt .Errorf ("failed to upsert thumbnail element: %v" , execErr )
273+ }
274+
275+ return nil
276+ }
277+
168278// IntegrationTest runs the integration tests based on the config file
169279func TestIntegration (t * testing.T ) {
170280 testenv .RunJSONSuite (t , testenv.JSONSuiteOptions {
@@ -175,7 +285,7 @@ func TestIntegration(t *testing.T) {
175285 },
176286 testenv .ActionAssertSubmodelAbsent : testenv .NewCheckSubmodelAbsentAction (testenv.CheckSubmodelAbsentOptions {
177287 Driver : "postgres" ,
178- DSN : "postgres://admin:admin123@127.0.0.1:6432/basyxTestDB?sslmode=disable" ,
288+ DSN : integrationTestDSN ,
179289 }),
180290 },
181291 StepName : func (step testenv.JSONSuiteStep , stepNumber int ) string {
@@ -219,7 +329,78 @@ func TestThumbnailAttachmentOperations(t *testing.T) {
219329 t .Logf ("Thumbnail content verified: %d bytes" , len (content ))
220330 })
221331
222- t .Run ("3_Delete_Thumbnail" , func (t * testing.T ) {
332+ t .Run ("3_Get_AAS_By_ID_Includes_Thumbnail_In_AssetInformation" , func (t * testing.T ) {
333+ aasEndpoint := fmt .Sprintf ("%s/shells/%s" , baseURL , aasIdentifier )
334+ payload , getStatus , getErr := getJSONResponse (aasEndpoint )
335+ require .NoError (t , getErr , "AAS retrieval failed" )
336+ assert .Equal (t , http .StatusOK , getStatus , "Expected 200 OK for AAS retrieval" )
337+
338+ assetInformation , ok := payload ["assetInformation" ].(map [string ]any )
339+ require .True (t , ok , "assetInformation should be present" )
340+
341+ thumbnail , ok := assetInformation ["thumbnail" ].(map [string ]any )
342+ require .True (t , ok , "assetInformation.thumbnail should be present" )
343+
344+ thumbnailPath , ok := thumbnail ["path" ].(string )
345+ require .True (t , ok , "thumbnail.path should be a string" )
346+ assert .NotEmpty (t , thumbnailPath , "thumbnail.path should not be empty" )
347+
348+ thumbnailContentType , ok := thumbnail ["contentType" ].(string )
349+ require .True (t , ok , "thumbnail.contentType should be a string" )
350+ assert .Equal (t , "image/gif" , thumbnailContentType , "thumbnail.contentType should match uploaded file" )
351+ })
352+
353+ t .Run ("4_Get_AAS_List_Includes_Thumbnail_In_AssetInformation" , func (t * testing.T ) {
354+ listEndpoint := fmt .Sprintf ("%s/shells" , baseURL )
355+ payload , getStatus , getErr := getJSONResponse (listEndpoint )
356+ require .NoError (t , getErr , "AAS list retrieval failed" )
357+ assert .Equal (t , http .StatusOK , getStatus , "Expected 200 OK for AAS list retrieval" )
358+
359+ result , ok := payload ["result" ].([]any )
360+ require .True (t , ok , "result should be an array" )
361+
362+ foundAAS := false
363+ for _ , entry := range result {
364+ aasMap , ok := entry .(map [string ]any )
365+ if ! ok {
366+ continue
367+ }
368+ if aasMap ["id" ] != aasID {
369+ continue
370+ }
371+
372+ foundAAS = true
373+ assetInformation , ok := aasMap ["assetInformation" ].(map [string ]any )
374+ require .True (t , ok , "assetInformation should be present in listed AAS" )
375+
376+ thumbnail , ok := assetInformation ["thumbnail" ].(map [string ]any )
377+ require .True (t , ok , "assetInformation.thumbnail should be present in listed AAS" )
378+
379+ thumbnailPath , ok := thumbnail ["path" ].(string )
380+ require .True (t , ok , "thumbnail.path should be a string in listed AAS" )
381+ assert .NotEmpty (t , thumbnailPath , "thumbnail.path should not be empty in listed AAS" )
382+
383+ thumbnailContentType , ok := thumbnail ["contentType" ].(string )
384+ require .True (t , ok , "thumbnail.contentType should be a string in listed AAS" )
385+ assert .Equal (t , "image/gif" , thumbnailContentType , "thumbnail.contentType should match uploaded file in listed AAS" )
386+ break
387+ }
388+
389+ assert .True (t , foundAAS , "Expected uploaded AAS to be present in list response" )
390+ })
391+
392+ t .Run ("5_Get_Thumbnail_Redirects_For_External_URL" , func (t * testing.T ) {
393+ externalThumbnailURL := "https://example.com/assets/thumbs/thumbnail-external.gif"
394+ setErr := setExternalThumbnailForAAS (aasID , externalThumbnailURL )
395+ require .NoError (t , setErr , "Failed to set external thumbnail URL" )
396+
397+ statusCode , locationHeader , requestErr := getThumbnailWithoutFollowingRedirect (thumbnailEndpoint )
398+ require .NoError (t , requestErr , "GET thumbnail request failed" )
399+ assert .Equal (t , http .StatusFound , statusCode , "Expected 302 Found for external thumbnail URL" )
400+ assert .Equal (t , externalThumbnailURL , locationHeader , "Expected redirect Location header for external thumbnail URL" )
401+ })
402+
403+ t .Run ("6_Delete_Thumbnail" , func (t * testing.T ) {
223404 req , reqErr := http .NewRequest (http .MethodDelete , thumbnailEndpoint , nil )
224405 require .NoError (t , reqErr , "Failed to create DELETE request" )
225406
@@ -231,13 +412,13 @@ func TestThumbnailAttachmentOperations(t *testing.T) {
231412 assert .Equal (t , http .StatusNoContent , resp .StatusCode , "Expected 204 No Content for thumbnail deletion" )
232413 })
233414
234- t .Run ("4_Verify_Thumbnail_Deleted " , func (t * testing.T ) {
415+ t .Run ("7_Verify_Thumbnail_Deleted " , func (t * testing.T ) {
235416 _ , _ , getStatus , getErr := downloadThumbnail (thumbnailEndpoint )
236417 require .NoError (t , getErr , "Thumbnail download after delete should not fail at HTTP level" )
237418 assert .Equal (t , http .StatusNotFound , getStatus , "Expected 404 Not Found after thumbnail deletion" )
238419 })
239420
240- t .Run ("5_Upload_Thumbnail_For_NonExisting_AAS " , func (t * testing.T ) {
421+ t .Run ("8_Upload_Thumbnail_For_NonExisting_AAS " , func (t * testing.T ) {
241422 nonExistingID := base64 .RawStdEncoding .EncodeToString ([]byte ("https://example.com/ids/aas/non_existing_thumbnail_test" ))
242423 nonExistingEndpoint := fmt .Sprintf ("%s/shells/%s/asset-information/thumbnail" , baseURL , nonExistingID )
243424
0 commit comments