diff --git a/go.mod b/go.mod index 6d471a3d80..2d60f79ce7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.0 require ( github.com/0chain/errors v1.0.3 - github.com/0chain/gosdk v1.20.9 + github.com/0chain/gosdk v1.20.10-0.20251207130329-236a4b0fb2f3 github.com/go-resty/resty/v2 v2.7.0 github.com/herumi/bls-go-binary v1.31.0 github.com/shopspring/decimal v1.3.1 diff --git a/go.sum b/go.sum index a2cc11fb6c..da8bf38f89 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,10 @@ github.com/0chain/common v1.18.3 h1:42dYOv2KyMTSanuS67iDtfv+ErbSRqR8NJ3MG72MwaI= github.com/0chain/common v1.18.3/go.mod h1:Lapu2Tj7z5Sm4r+X141e7vsz4NDODTEypeElYAP3iSw= github.com/0chain/errors v1.0.3 h1:QQZPFxTfnMcRdt32DXbzRQIfGWmBsKoEdszKQDb0rRM= github.com/0chain/errors v1.0.3/go.mod h1:xymD6nVgrbgttWwkpSCfLLEJbFO6iHGQwk/yeSuYkIc= -github.com/0chain/gosdk v1.20.9 h1:pt4dhlu605qjVQ5PvKcPfKgHjMCNdqnPYb4HOpCwnfg= -github.com/0chain/gosdk v1.20.9/go.mod h1:dwDhPmkbmcTqbOcTpFZqcuKPZBa7Eq79vz5bNLRtxxo= +github.com/0chain/gosdk v1.20.10-0.20251121073326-a9a97301edc9 h1:+ueIV8IUPDOmvSPz4lhEb+Ms3JGjgH+HBWgUTa8uR6g= +github.com/0chain/gosdk v1.20.10-0.20251121073326-a9a97301edc9/go.mod h1:dwDhPmkbmcTqbOcTpFZqcuKPZBa7Eq79vz5bNLRtxxo= +github.com/0chain/gosdk v1.20.10-0.20251207130329-236a4b0fb2f3 h1:f02irbxFclAzukSm3ozq8cKhjEYxRj6C8eZD+PkfHCY= +github.com/0chain/gosdk v1.20.10-0.20251207130329-236a4b0fb2f3/go.mod h1:dwDhPmkbmcTqbOcTpFZqcuKPZBa7Eq79vz5bNLRtxxo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= diff --git a/internal/api/model/zbox.go b/internal/api/model/zbox.go index 95e397c98d..bc1e1ccdb8 100644 --- a/internal/api/model/zbox.go +++ b/internal/api/model/zbox.go @@ -201,6 +201,31 @@ type ZboxJwtToken struct { JwtToken string `json:"jwt_token"` } +// TranscodingEntity represents the transcoding entity metadata +type TranscodingEntity struct { + ID int64 `json:"ID"` + UserID string `json:"UserID"` + ClientID string `json:"ClientID"` + Remotepath string `json:"Remotepath"` + FilePath string `json:"FilePath"` + AllocationID string `json:"AllocationID"` + FileName string `json:"FileName"` + Mode string `json:"Mode"` + FileSize int64 `json:"FileSize"` + DoThumbnail bool `json:"DoThumbnail"` + Status int `json:"Status"` + AppType int `json:"AppType"` + CreatedAt string `json:"CreatedAt"` + UpdatedAt string `json:"UpdatedAt"` + Deleted *string `json:"Deleted"` +} + +// GetTranscodingEntityResponse wraps API responses for GET metadata endpoint +type GetTranscodingEntityResponse struct { + Message string `json:"message"` + Data TranscodingEntity `json:"data"` +} + type ReferralCodeOfUser struct { ReferrerCode string `json:"referral_code"` ReferrerLink string `json:"referral_link"` diff --git a/internal/api/util/client/zbox_client.go b/internal/api/util/client/zbox_client.go index 044dae167f..152c4ace09 100644 --- a/internal/api/util/client/zbox_client.go +++ b/internal/api/util/client/zbox_client.go @@ -1,6 +1,7 @@ package client import ( + "encoding/json" "fmt" "strconv" @@ -283,6 +284,11 @@ func (c *ZboxClient) CreateWallet(t *test.SystemTest, headers, wallet map[string require.NoError(t, err, "URL parse error") urlBuilder.SetPath("/v2/wallet") + // t.Logf("wallet input: %v", wallet) + // t.Logf("CreateWallet URL: %v", urlBuilder.String()) + // t.Logf("CreateWallet Headers: %v", headers) + // t.Logf("CreateWallet Wallet: %v", wallet) + resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ Dst: &zboxWallet, FormData: wallet, @@ -1470,3 +1476,135 @@ func (c *ZboxClient) GetCSRFToken(t *test.SystemTest) (*model.CSRFToken, *resty. return csrfToken, resp, err } + +// CreateMetadata posts transcoding metadata to the server and returns parsed response. +func (c *ZboxClient) CreateMetadata(t *test.SystemTest, headers map[string]string, body map[string]interface{}) (*model.TranscodingEntity, *resty.Response, error) { + t.Logf("creating transcoding metadata for user [%v] using 0box...", headers["X-App-User-ID"]) + + // Log request body + bodyJSON, err := json.Marshal(body) + if err == nil { + t.Logf("CreateMetadata request body: %s", string(bodyJSON)) + } else { + t.Logf("CreateMetadata request body (marshal error): %v", body) + } + + var res *model.GetTranscodingEntityResponse + + urlBuilder := NewURLBuilder() + err = urlBuilder.MustShiftParse(c.zboxEntrypoint) + require.NoError(t, err, "URL parse error") + urlBuilder.SetPath("/v2/metadata") + + // Ensure Content-Type is set to JSON + jsonHeaders := make(map[string]string) + for k, v := range headers { + jsonHeaders[k] = v + } + jsonHeaders["Content-Type"] = "application/json" + + resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ + Dst: &res, + Headers: jsonHeaders, + Body: body, + RequiredStatusCode: 201, + }, HttpPOSTMethod) + + if err != nil { + t.Errorf("CreateMetadata response error: %v, response body: %s", err, string(resp.Body())) + return nil, resp, err + } + + // Log response body + t.Logf("CreateMetadata response status: %d, response body: %s", resp.StatusCode(), string(resp.Body())) + + if res != nil && res.Data.ID != 0 { + return &res.Data, resp, nil + } + + return nil, resp, fmt.Errorf("transcoding entity not found in response") +} + +// UpdateUploadStatus updates status for an uploaded file and returns parsed response. +func (c *ZboxClient) UpdateUploadStatus(t *test.SystemTest, headers map[string]string, body map[string]interface{}) (*model.TranscodingEntity, *resty.Response, error) { + t.Logf("updating upload status for user [%v] using 0box...", headers["X-App-User-ID"]) + + // Log request body + bodyJSON, err := json.Marshal(body) + if err == nil { + t.Logf("UpdateUploadStatus request body: %s", string(bodyJSON)) + } else { + t.Logf("UpdateUploadStatus request body (marshal error): %v", body) + } + + var res *model.GetTranscodingEntityResponse + + urlBuilder := NewURLBuilder() + err = urlBuilder.MustShiftParse(c.zboxEntrypoint) + require.NoError(t, err, "URL parse error") + urlBuilder.SetPath("/v2/updateUploadStatus") + + // Ensure Content-Type is set to JSON + jsonHeaders := make(map[string]string) + for k, v := range headers { + jsonHeaders[k] = v + } + jsonHeaders["Content-Type"] = "application/json" + + resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ + Dst: &res, + Headers: jsonHeaders, + Body: body, + RequiredStatusCode: 201, + }, HttpPUTMethod) + + if err != nil { + t.Errorf("UpdateUploadStatus response error: %v, response body: %s", err, string(resp.Body())) + return nil, resp, err + } + + // Log response body + t.Logf("UpdateUploadStatus response status: %d, response body: %s", resp.StatusCode(), string(resp.Body())) + + if res != nil && res.Data.ID != 0 { + return &res.Data, resp, nil + } + + return nil, resp, fmt.Errorf("transcoding entity not found in response") +} + +// GetMetadata gets transcoding entity metadata and returns parsed response. +func (c *ZboxClient) GetMetadata(t *test.SystemTest, headers map[string]string, queryParams map[string]string) (*model.TranscodingEntity, *resty.Response, error) { + t.Logf("getting transcoding metadata for user [%v] using 0box...", headers["X-App-User-ID"]) + + // Log query parameters + t.Logf("GetMetadata query params: %v", queryParams) + + var res *model.GetTranscodingEntityResponse + + urlBuilder := NewURLBuilder() + err := urlBuilder.MustShiftParse(c.zboxEntrypoint) + require.NoError(t, err, "URL parse error") + urlBuilder.SetPath("/v2/metadata") + + resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ + Dst: &res, + Headers: headers, + QueryParams: queryParams, + RequiredStatusCode: 200, + }, HttpGETMethod) + + if err != nil { + t.Errorf("GetMetadata response error: %v, response body: %s", err, string(resp.Body())) + return nil, resp, err + } + + // Log response body + t.Logf("GetMetadata response status: %d, response body: %s", resp.StatusCode(), string(resp.Body())) + + if res != nil && res.Data.ID != 0 { + return &res.Data, resp, nil + } + + return nil, resp, fmt.Errorf("transcoding entity not found in response") +} diff --git a/tests/api_tests/0box_transcoder_test.go b/tests/api_tests/0box_transcoder_test.go new file mode 100644 index 0000000000..784c8b5622 --- /dev/null +++ b/tests/api_tests/0box_transcoder_test.go @@ -0,0 +1,635 @@ +package api_tests + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/0chain/gosdk/constants" + "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zboxcore/sdk" + "github.com/0chain/system_test/internal/api/model" + "github.com/0chain/system_test/internal/api/util/client" + "github.com/0chain/system_test/internal/api/util/test" + "github.com/stretchr/testify/require" + "gopkg.in/errgo.v2/errors" + // coreClient "github.com/0chain/gosdk/core/client" +) + +func Test0BoxTranscoder(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + t.Logf("Starting 0Box Transcoder Tests...") + + // Create a new wallet + headers := map[string]string{ + "X-App-Client-ID": sdkWallet.ClientID, + "X-App-Client-Key": sdkWallet.ClientKey, + "X-App-Timestamp": client.X_APP_TIMESTAMP, + "X-App-ID-TOKEN": client.X_APP_ID_TOKEN, + "X-App-User-ID": client.X_APP_USER_ID, + "X-CSRF-TOKEN": client.X_APP_CSRF, + "X-App-Client-Signature": client.X_APP_CLIENT_SIGNATURE, + "X-APP-TYPE": client.X_APP_BLIMP, + } + Teardown(t, headers) + err := Create0boxTestWallet(t, headers) + require.NoError(t, err) + t.Logf("0box test wallet created: %s", headers["X-App-Client-ID"]) + + t.Logf("Generating split key...") + jwtToken, response, err := zboxClient.CreateJwtToken(t, headers) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "Response status code does not match expected. Output: [%v]", response.String()) + + zVaultHeaders := zvaultClient.NewZvaultHeaders(jwtToken.JwtToken) + zVaultHeaders["X-User-ID"] = client.X_APP_USER_ID + + response, err = zvaultClient.Store(t, sdkWallet.Keys[0].PrivateKey, sdkWallet.Mnemonic, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "StoreHandler: Response status code does not match expected. Output: [%v]", response.String()) + t.Logf("Wallet stored with StoreHandler: %v", response.String()) + + t.Logf("Generating split key for client id: %s...", sdkWallet.ClientID) + response, err = zvaultClient.GenerateSplitKey(t, sdkWallet.ClientID, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "GenerateSplitKey: Status code does not match expected. Output: [%v]", response.String()) + t.Logf("Generated split key for client id: %s", sdkWallet.ClientID) + + t.Logf("Getting keys for client id: %s...", sdkWallet.ClientID) + keys, response, err := zvaultClient.GetKeys(t, sdkWallet.ClientID, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "GetKeys after StoreHandler: Status code does not match expected. Output: [%v]", response.String()) + require.Len(t, keys.Keys, 1) + require.Equal(t, sdkWallet.ClientID, keys.Keys[0].ClientID, "Stored key's clientID: %s does not match sdkWallet clientID: %s", keys.Keys[0].ClientID, sdkWallet.ClientID) + t.Logf("Verified key's clientID: %s", keys.Keys[0].ClientID) + + t.Cleanup(func() { + t.Logf("Deleting wallet for client id: %s...", sdkWallet.ClientID) + response, err = zvaultClient.Delete(t, sdkWallet.ClientID, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "Delete: Response status code does not match expected. Output: [%v]", response.String()) + t.Logf("Wallet deleted for client id: %s", sdkWallet.ClientID) + }) + + t.Logf("sharing wallet to 0box-server...") + response, err = zvaultClient.ShareWallet(t, "0box-server", keys.Keys[0].PublicKey, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "Response status code does not match expected. Output: [%v]", response.String()) + + t.Logf("retrieving split keys...") + keys, response, err = zvaultClient.GetKeys(t, sdkWallet.ClientID, zVaultHeaders) + require.NoError(t, err) + require.Equal(t, 200, response.StatusCode(), "Response status code does not match expected. Output: [%v]", response.String()) + require.Equal(t, keys.Keys[0].ClientID, sdkWallet.ClientID) + + var sharedKey *model.SplitKey + for i, k := range keys.Keys { + t.Logf("split key[%d]: %v", i, *k) + t.Logf("SharedTo[%d]: %v", i, k.SharedTo) + if k.SharedTo == "0box-server" { + sharedKey = k + break + } + } + require.NotNil(t, sharedKey, "Shared key to 0box-server not found") + t.Logf("sharedKey: %v, splitKeys len %d", *sharedKey, len(keys.Keys)) + + // create allocation + t.Logf("creating new allocation through SDK...") + var ( + readPrice = sdk.PriceRange{Min: 0, Max: 0} + writePrice = sdk.PriceRange{Min: 0, Max: 250000000} + ) + options := sdk.CreateAllocationOptions{ + DataShards: 1, + ParityShards: 1, + Size: 2147483648, // 2GB + ReadPrice: sdk.PriceRange{ + Min: readPrice.Min, + Max: readPrice.Max, + }, + WritePrice: sdk.PriceRange{ + Min: writePrice.Min, + Max: writePrice.Max, + }, + FileOptionsParams: &sdk.FileOptionsParameters{}, + Lock: 1000000000, + } + t.Logf("creating new allocation with options: %+v", options) + allocationID, _, _, err := sdk.CreateAllocationWith(options) + require.NoError(t, err, "Failed to create allocation with SDK") + t.Logf("newallocation created with ID: %v", allocationID) + + // t.Cleanup(func() { + // t.Logf("deleting allocation: %s", allocationID) + // _, _, err := sdk.CancelAllocation(allocationID) + // require.NoError(t, err, "Failed to cancel allocation with SDK") + // t.Logf("allocation cancelled: %s", allocationID) + // }) + + // Run transcode subtests (they will run in parallel because RunSequentially now delegates to Run) + timeout := 3 * time.Minute + sleepTime := 20 * time.Second + t.RunWithTimeout("Transcode MP4 file with web mode", timeout, func(t *test.SystemTest) { + // Test MP4 transcoding + fileName := "sample.mp4" + transcodeFile(t, headers, allocationID, sharedKey, fileName, "web") + + // wait before first verification (20 seconds) then verify up to 3 times + time.Sleep(sleepTime) + queryParams := map[string]string{ + "app_type": headers["X-APP-TYPE"], + "file_path": fmt.Sprintf("/%s/%s", fileName, fileName), + "mode": "web", + } + + var verr error + for i := 0; i < 3; i++ { + t.Logf("verify attempt %d for sample.mp4", i+1) + + // First verify metadata status + entity, response, err := zboxClient.GetMetadata(t, headers, queryParams) + if err != nil { + t.Logf("metadata verification attempt %d failed: %v, sleeping %s before retry", i+1, err, sleepTime) + time.Sleep(sleepTime) + continue + } + if response.StatusCode() != 200 { + t.Logf("metadata verification attempt %d failed: status code %d, sleeping %s before retry", i+1, response.StatusCode(), sleepTime) + time.Sleep(sleepTime) + continue + } + if entity == nil { + t.Logf("metadata verification attempt %d failed: entity is nil, sleeping %s before retry", i+1, sleepTime) + time.Sleep(sleepTime) + continue + } + if entity.Status != 6 { + t.Logf("metadata verification attempt %d: status is %d (expected 6), sleeping %s before retry", i+1, entity.Status, sleepTime) + time.Sleep(sleepTime) + continue + } + t.Logf("Transcoding entity status verified: %d", entity.Status) + + // Metadata status is 6, proceed to verify transcoded file + verr = verifyTranscodedFile(t, allocationID, fmt.Sprintf("/%s/preview", fileName), sharedKey) + if verr == nil { + break + } + t.Logf("file verification attempt %d failed: %v, sleeping %s before retry", i+1, verr, sleepTime) + time.Sleep(sleepTime) + } + require.NoError(t, verr, "verification failed after 3 attempts") + }) + + t.RunWithTimeout("Transcode AVI file with web mode", timeout, func(t *test.SystemTest) { + // Test AVI transcoding + fileName := "sample.avi" + transcodeFile(t, headers, allocationID, sharedKey, fileName, "web") + + // wait before first verification (20 seconds) then verify up to 3 times + time.Sleep(sleepTime) + queryParams := map[string]string{ + "app_type": headers["X-APP-TYPE"], + "file_path": fmt.Sprintf("/%s/%s", fileName, fileName), + "mode": "web", + } + + var verr error + for i := 0; i < 3; i++ { + t.Logf("verify attempt %d for sample.avi", i+1) + + // First verify metadata status + entity, response, err := zboxClient.GetMetadata(t, headers, queryParams) + if err != nil { + t.Logf("metadata verification attempt %d failed: %v, sleeping %s before retry", i+1, err, sleepTime) + time.Sleep(sleepTime) + continue + } + if response.StatusCode() != 200 { + t.Logf("metadata verification attempt %d failed: status code %d, sleeping %s before retry", i+1, response.StatusCode(), sleepTime) + time.Sleep(sleepTime) + continue + } + if entity == nil { + t.Logf("metadata verification attempt %d failed: entity is nil, sleeping %s before retry", i+1, sleepTime) + time.Sleep(sleepTime) + continue + } + if entity.Status != 6 { + t.Logf("metadata verification attempt %d: status is %d (expected 6), sleeping %s before retry", i+1, entity.Status, sleepTime) + time.Sleep(sleepTime) + continue + } + t.Logf("Transcoding entity status verified: %d", entity.Status) + + // Metadata status is 6, proceed to verify transcoded file + verr = verifyTranscodedFile(t, allocationID, fmt.Sprintf("/%s/preview", fileName), sharedKey) + if verr == nil { + break + } + t.Logf("file verification attempt %d failed: %v, sleeping %s before retry", i+1, verr, sleepTime) + time.Sleep(sleepTime) + } + require.NoError(t, verr, "verification failed after 3 attempts") + }) + + t.RunWithTimeout("Transcode MOV file with web mode", timeout, func(t *test.SystemTest) { + // Test MOV transcoding + fileName := "sample.mov" + transcodeFile(t, headers, allocationID, sharedKey, fileName, "web") + + // wait before first verification (20 seconds) then verify up to 3 times + time.Sleep(sleepTime) + queryParams := map[string]string{ + "app_type": headers["X-APP-TYPE"], + "file_path": fmt.Sprintf("/%s/%s", fileName, fileName), + "mode": "web", + } + + var verr error + for i := 0; i < 3; i++ { + t.Logf("verify attempt %d for sample.mov", i+1) + + // First verify metadata status + entity, response, err := zboxClient.GetMetadata(t, headers, queryParams) + if err != nil { + t.Logf("metadata verification attempt %d failed: %v, sleeping %s before retry", i+1, err, sleepTime) + time.Sleep(sleepTime) + continue + } + if response.StatusCode() != 200 { + t.Logf("metadata verification attempt %d failed: status code %d, sleeping %s before retry", i+1, response.StatusCode(), sleepTime) + time.Sleep(sleepTime) + continue + } + if entity == nil { + t.Logf("metadata verification attempt %d failed: entity is nil, sleeping %s before retry", i+1, sleepTime) + time.Sleep(sleepTime) + continue + } + if entity.Status != 6 { + t.Logf("metadata verification attempt %d: status is %d (expected 6), sleeping %s before retry", i+1, entity.Status, sleepTime) + time.Sleep(sleepTime) + continue + } + t.Logf("Transcoding entity status verified: %d", entity.Status) + + // Metadata status is 6, proceed to verify transcoded file + verr = verifyTranscodedFile(t, allocationID, fmt.Sprintf("/%s/preview", fileName), sharedKey) + if verr == nil { + break + } + t.Logf("file verification attempt %d failed: %v, sleeping %s before retry", i+1, verr, sleepTime) + time.Sleep(sleepTime) + } + require.NoError(t, verr, "verification failed after 3 attempts") + }) + + t.RunWithTimeout("Transcode MP3 file with web mode", timeout, func(t *test.SystemTest) { + // Test MP3 transcoding + fileName := "audio.mp3" + transcodeFile(t, headers, allocationID, sharedKey, fileName, "web") + + // wait before first verification (20 seconds) then verify up to 3 times + time.Sleep(sleepTime) + queryParams := map[string]string{ + "app_type": headers["X-APP-TYPE"], + "file_path": fmt.Sprintf("/%s/%s", fileName, fileName), + "mode": "web", + } + + var verr error + for i := 0; i < 3; i++ { + t.Logf("verify attempt %d for audio.mp3", i+1) + + // First verify metadata status + entity, response, err := zboxClient.GetMetadata(t, headers, queryParams) + if err != nil { + t.Logf("metadata verification attempt %d failed: %v, sleeping %s before retry", i+1, err, sleepTime) + time.Sleep(sleepTime) + continue + } + if response.StatusCode() != 200 { + t.Logf("metadata verification attempt %d failed: status code %d, sleeping %s before retry", i+1, response.StatusCode(), sleepTime) + time.Sleep(sleepTime) + continue + } + if entity == nil { + t.Logf("metadata verification attempt %d failed: entity is nil, sleeping %s before retry", i+1, sleepTime) + time.Sleep(sleepTime) + continue + } + if entity.Status != 6 { + t.Logf("metadata verification attempt %d: status is %d (expected 6), sleeping %s before retry", i+1, entity.Status, sleepTime) + time.Sleep(sleepTime) + continue + } + t.Logf("Transcoding entity status verified: %d", entity.Status) + + // Metadata status is 6, proceed to verify transcoded file + verr = verifyTranscodedFile(t, allocationID, fmt.Sprintf("/%s/preview", fileName), sharedKey) + if verr == nil { + break + } + t.Logf("file verification attempt %d failed: %v, sleeping %s before retry", i+1, verr, sleepTime) + time.Sleep(sleepTime) + } + require.NoError(t, verr, "verification failed after 3 attempts") + }) + + t.RunWithTimeout("Transcode unsupported file should fail", timeout, func(t *test.SystemTest) { + // Create a random file for testing failure case + curDir, err := os.Getwd() + require.NoError(t, err, "Unable to get working directory") + testFilesDir := filepath.Join(curDir, "test_files_small") + + randomFileName := fmt.Sprintf("random_unsupported_%d.bin", time.Now().UnixNano()) + randomFilePath := filepath.Join(testFilesDir, randomFileName) + + // Create a random binary file + randomFile, err := os.Create(randomFilePath) + require.NoError(t, err, "Failed to create random file") + + // Write some random data + randomData := make([]byte, 1024) + for i := range randomData { + randomData[i] = byte(i % 256) + } + _, err = randomFile.Write(randomData) + require.NoError(t, err, "Failed to write random data") + randomFile.Close() + + // Clean up the file after test + defer func() { + _ = os.Remove(randomFilePath) + }() + + // Test transcoding of unsupported file + transcodeFile(t, headers, allocationID, sharedKey, randomFileName, "web") + + // wait before first verification (20 seconds) then verify up to 3 times + time.Sleep(sleepTime) + queryParams := map[string]string{ + "app_type": headers["X-APP-TYPE"], + "file_path": fmt.Sprintf("/%s/%s", randomFileName, randomFileName), + "mode": "web", + } + + var finalStatus int + for i := 0; i < 3; i++ { + t.Logf("verify attempt %d for %s (expecting failure status 5)", i+1, randomFileName) + + // Verify metadata status - expecting failure status (5) + entity, response, err := zboxClient.GetMetadata(t, headers, queryParams) + if err != nil { + t.Logf("metadata verification attempt %d failed: %v, sleeping %s before retry", i+1, err, sleepTime) + time.Sleep(sleepTime) + continue + } + if response.StatusCode() != 200 { + t.Logf("metadata verification attempt %d failed: status code %d, sleeping %s before retry", i+1, response.StatusCode(), sleepTime) + time.Sleep(sleepTime) + continue + } + if entity == nil { + t.Logf("metadata verification attempt %d failed: entity is nil, sleeping %s before retry", i+1, sleepTime) + time.Sleep(sleepTime) + continue + } + finalStatus = entity.Status + if entity.Status == 5 { + t.Logf("Transcoding entity status verified as failure: %d", entity.Status) + break + } + t.Logf("metadata verification attempt %d: status is %d (expected 5), sleeping %s before retry", i+1, entity.Status, sleepTime) + time.Sleep(sleepTime) + } + require.Equal(t, 5, finalStatus, "transcoding status should be failure (5), got: %d", finalStatus) + }) + +} + +// transcodeFile makes the actual API call to the transcoder endpoint +func transcodeFile(t *test.SystemTest, headers map[string]string, allocationID string, splitKey *model.SplitKey, fileName, mode string) { + t.Logf("transcoding file: %s with mode: %s", fileName, mode) + + t.Logf("splitKey fields: ClientID=%s, PublicKey=%s, PrivateKey=%s, UserID=%s, PeerPublicKey=%s", + splitKey.ClientID, splitKey.PublicKey, splitKey.PrivateKey, splitKey.UserID, splitKey.PeerPublicKey) + + allocation, err := sdk.GetAllocation(allocationID) + require.NoError(t, err, "Failed to get allocation from SDK") + + // Create directory at remotepath "/{fileName}" + remotepath := fmt.Sprintf("/%s", fileName) + createDirOp := sdk.OperationRequest{ + OperationType: constants.FileOperationCreateDir, + RemotePath: remotepath, + } + err = allocation.DoMultiOperation([]sdk.OperationRequest{createDirOp}) + require.NoError(t, err, "Failed to create directory") + + // Use the test_files_small folder as the workDir for uploads + curDir, err := os.Getwd() + require.NoError(t, err, "Unable to get working directory") + workDir := filepath.Join(curDir, "test_files_small") + + // Upload file to "/{fileName}" + err = UploadFileBlobber(t, *sdkWallet, allocation, workDir, remotepath, fileName) + require.NoError(t, err, "upload failed: %v", err) + + // Determine remote name and file size for metadata + fi, err := os.Stat(filepath.Join(workDir, fileName)) + require.NoError(t, err) + actualSize := fi.Size() + + // Metadata request with new structure + metaBody := map[string]interface{}{ + "remotepath": remotepath, + "mode": mode, + "allocation_id": allocationID, + "file_name": fileName, + "file_size": actualSize, + "file_path": fmt.Sprintf("/%s/%s", fileName, fileName), + "do_thumbnail": false, + } + + transcodingEntity, response, metaErr := zboxClient.CreateMetadata(t, headers, metaBody) + require.NoError(t, metaErr, "createMetadata API call failed: %v", metaErr) + require.Equal(t, 201, response.StatusCode(), "createMetadata API call failed: %v", response.String()) + require.NotNil(t, transcodingEntity, "transcodingEntity is nil") + t.Logf("transcodingEntity: %v", *transcodingEntity) + + // Update upload status + updateBody := map[string]interface{}{ + "id": transcodingEntity.ID, + "status": 1, + } + transcodingEntity, response, updateErr := zboxClient.UpdateUploadStatus(t, headers, updateBody) + require.NoError(t, updateErr, "updateUploadStatus API call failed: %v", updateErr) + require.Equal(t, 201, response.StatusCode(), "updateUploadStatus API call failed: %v", response.String()) + require.NotNil(t, transcodingEntity, "transcodingEntity is nil") + t.Logf("transcodingEntity: %v", *transcodingEntity) + + t.Logf("Metadata post completed; fileName : %v, size_in_bytes; %v", fileName, actualSize) + +} + +func verifyTranscodedFile(t *test.SystemTest, allocationID, remotpath string, splitKey *model.SplitKey) error { + t.Logf("verifying transcoded file at remote path: %s", remotpath) + + // List directory contents + allocationObj, err := sdk.GetAllocation(allocationID) + if err != nil { + return fmt.Errorf("failed to get allocation from SDK: %w", err) + } + + listResult, err := allocationObj.ListDir(remotpath) + if err != nil { + return fmt.Errorf("failed to list directory: %w", err) + } + + if listResult == nil { + return fmt.Errorf("list result is nil") + } + + // Log directory information + t.Logf("Transcoded Directory Info: path: %s", remotpath) + t.Logf("Directory Type: %s, Name: %s, Path: %s, Size: %d bytes, Hash: %s, FileMetaHash: %v, ActualSize: %d, NumFiles: %d, Directory Children: %d", + listResult.Type, listResult.Name, listResult.Path, listResult.Size, listResult.Hash, listResult.FileMetaHash, listResult.ActualSize, listResult.NumFiles, len(listResult.Children)) + + // Calculate total size and print all fields for each file + var totalSize int64 + if len(listResult.Children) > 0 { + t.Logf("Files in directory (%d):", len(listResult.Children)) + for i, child := range listResult.Children { + // Only count files (type "f"), not directories + if child.Type == "f" { + totalSize += child.Size + } + + // Print all fields for each file using JSON marshaling to show all available fields + childJSON, err := json.MarshalIndent(child, " ", " ") + if err == nil { + t.Logf(" File %d (all fields):", i+1) + t.Logf(" %s", string(childJSON)) + } else { + // Fallback: print basic fields if JSON marshaling fails + t.Logf(" File %d:", i+1) + t.Logf(" Type: %s", child.Type) + t.Logf(" Name: %s", child.Name) + t.Logf(" Path: %s", child.Path) + t.Logf(" Size: %d bytes", child.Size) + t.Logf(" Hash: %s", child.Hash) + t.Logf(" LookupHash: %s", child.LookupHash) + t.Logf(" MimeType: %s", child.MimeType) + t.Logf(" NumBlocks: %d", child.NumBlocks) + } + } + } else { + t.Logf("No files found in directory") + } + + t.Logf("Total directory size: %d bytes", totalSize) + + return nil +} + +func UploadFileBlobber(t *test.SystemTest, wallet zcncrypto.Wallet, allocationObj *sdk.Allocation, workDir, remotePath, fileName string) error { + + wg := &sync.WaitGroup{} + wg.Add(1) + cb := &StatusBar{wg: wg, t: t} + + localSlice := []string{filepath.Join(workDir, fileName)} + fileNameSlice := []string{fileName} + thumbnailSlice := []string{""} + encrypts := []bool{false} + chunkNumbers := []int{0} + remoteSlice := []string{remotePath} + isUpdate := []bool{false} + isWebstreaming := []bool{false} + + err := allocationObj.StartMultiUpload(workDir, localSlice, fileNameSlice, thumbnailSlice, encrypts, chunkNumbers, remoteSlice, isUpdate, isWebstreaming, cb) + if err != nil { + return errors.New("upload failed: " + err.Error()) + } + + wg.Wait() + return nil +} + +// StatusBar is to check status of any operation +type StatusBar struct { + wg *sync.WaitGroup + success bool + err error + t *test.SystemTest + + totalBytes int + completedBytes int + callback func(totalBytes int, completedBytes int, err string) +} + +var jsCallbackMutex sync.Mutex + +// Started for statusBar +func (s *StatusBar) Started(allocationID, filePath string, op int, totalBytes int) { + s.totalBytes = totalBytes + if s.callback != nil { + jsCallbackMutex.Lock() + defer jsCallbackMutex.Unlock() + s.callback(s.totalBytes, s.completedBytes, "") + } +} + +// InProgress for statusBar +func (s *StatusBar) InProgress(allocationID, filePath string, op int, completedBytes int, todo_name_var []byte) { + s.completedBytes = completedBytes + if s.callback != nil { + jsCallbackMutex.Lock() + defer jsCallbackMutex.Unlock() + s.callback(s.totalBytes, s.completedBytes, "") + } +} + +// Completed for statusBar +func (s *StatusBar) Completed(allocationID, filePath string, filename string, mimetype string, size int, op int) { + s.success = true + + s.completedBytes = s.totalBytes + if s.callback != nil { + jsCallbackMutex.Lock() + defer jsCallbackMutex.Unlock() + s.callback(s.totalBytes, s.completedBytes, "") + } + + defer s.wg.Done() +} + +// Error for statusBar +func (s *StatusBar) Error(allocationID string, filePath string, op int, err error) { + s.success = false + s.err = err + defer func() { + if r := recover(); r != nil { + s.t.Errorf("Recovered in statusBar Error") + } + }() + s.t.Errorf("Error in file operation." + err.Error()) + if s.callback != nil { + jsCallbackMutex.Lock() + defer jsCallbackMutex.Unlock() + s.callback(s.totalBytes, s.completedBytes, err.Error()) + } + s.wg.Done() +} + +// RepairCompleted when repair is completed +func (s *StatusBar) RepairCompleted(filesRepaired int) { + s.wg.Done() +} diff --git a/tests/api_tests/main_test.go b/tests/api_tests/main_test.go index 055d18b9bb..32a40b2fea 100644 --- a/tests/api_tests/main_test.go +++ b/tests/api_tests/main_test.go @@ -12,6 +12,8 @@ import ( coreClient "github.com/0chain/gosdk/core/client" "github.com/0chain/gosdk/core/conf" + "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zcncore" "github.com/0chain/system_test/internal/api/model" "github.com/0chain/system_test/internal/api/util/client" @@ -36,6 +38,7 @@ var ( blobberOwnerWallet *model.Wallet blobberOwnerWalletMnemonics string parsedConfig *config.Config + sdkWallet *zcncrypto.Wallet initialisedWallets []*model.Wallet walletIdx int64 @@ -79,8 +82,24 @@ func TestMain(m *testing.M) { QuerySleepTime: 5, MinSubmit: 10, MinConfirmation: 10, + ZauthServer: parsedConfig.ZauthUrl, }) require.NoError(t, err) + coreClient.SetSdkInitialized(true) + + // err = coreClient.InitSDK( + // "{}", + // parsedConfig.BlockWorker, + // "0afc093ffb509f059c55478bc1a60351cef7b4e9c008a53a6cc8241ca8617dfe", + // "bls0chain", + // 0, true, + // ) + // if err != nil { + // log.Println("Error in sdk init", err) + // return + // } + + zcncore.RegisterZauthServer(parsedConfig.ZauthUrl) blobberOwnerWalletMnemonics = parsedConfig.BlobberOwnerWalletMnemonics blobberOwnerWallet = apiClient.CreateWalletForMnemonic(t, blobberOwnerWalletMnemonics) @@ -127,6 +146,40 @@ func TestMain(m *testing.M) { initialisedWallets = append(initialisedWallets, initialisedWallet) } + if len(initialisedWallets) > 0 { + walletMutex.Lock() + wallet := initialisedWallets[walletIdx] + walletIdx++ + walletMutex.Unlock() + + var dateCreated string + if wallet.CreationDate != nil { + dateCreated = time.Unix(int64(*wallet.CreationDate), 0).Format(time.RFC3339) + } + sdkWallet = &zcncrypto.Wallet{ + ClientID: wallet.Id, + ClientKey: wallet.PublicKey, + Keys: []zcncrypto.KeyPair{{ + PublicKey: wallet.Keys.PublicKey.SerializeToHexStr(), + PrivateKey: wallet.Keys.PrivateKey.SerializeToHexStr(), + }}, + Mnemonic: wallet.Mnemonics, + Version: wallet.Version, + DateCreated: dateCreated, + } + log.Printf("SDK wallet: %+v", sdkWallet) + wBytes, err := json.Marshal(sdkWallet) + if err != nil { + log.Println("Error serializing SDK wallet:", err) + } else if err = zcncore.SetGeneralWalletInfo(string(wBytes), "bls0chain"); err != nil { + log.Printf("Failed to set general wallet info: %v", err) + } else { + log.Printf("SDK wallet general info loaded for clientID %s", sdkWallet.ClientID) + } + } else { + log.Println("No wallets available to set SDK wallet") + } + os.Exit(m.Run()) } diff --git a/tests/api_tests/test_files_small/audio.mp3 b/tests/api_tests/test_files_small/audio.mp3 new file mode 100644 index 0000000000..2aaa2788d3 Binary files /dev/null and b/tests/api_tests/test_files_small/audio.mp3 differ diff --git a/tests/api_tests/test_files_small/sample.avi b/tests/api_tests/test_files_small/sample.avi new file mode 100644 index 0000000000..dabce398b2 Binary files /dev/null and b/tests/api_tests/test_files_small/sample.avi differ diff --git a/tests/api_tests/test_files_small/sample.mov b/tests/api_tests/test_files_small/sample.mov new file mode 100644 index 0000000000..a1002b120d Binary files /dev/null and b/tests/api_tests/test_files_small/sample.mov differ diff --git a/tests/api_tests/test_files_small/sample.mp4 b/tests/api_tests/test_files_small/sample.mp4 new file mode 100644 index 0000000000..0a4dd5b401 Binary files /dev/null and b/tests/api_tests/test_files_small/sample.mp4 differ