diff --git a/README.md b/README.md
index 480eb92..681cc54 100644
--- a/README.md
+++ b/README.md
@@ -1,163 +1,119 @@
-# openapi-java-client
+
+
+
+
-WaveSpeed AI API
-- API version: 0.0.1
- - Build date: 2025-04-07T16:39:33.625926313+08:00[Asia/Shanghai]
- - Generator version: 7.10.0
+
WaveSpeedAI Java SDK
-API for generating images using WaveSpeed AI
+
+ Official Java SDK for the WaveSpeedAI inference platform
+
+
+ 🌐 Visit wavespeed.ai •
+ 📖 Documentation •
+ 💬 Issues
+
+
-*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)*
-
-
-## Requirements
-
-Building the API client library requires:
-1. Java 1.8+
-2. Maven (3.8.3+)/Gradle (7.2+)
+---
## Installation
-To install the API client library to your local Maven repository, simply execute:
-
-```shell
-mvn clean install
-```
-
-To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
-
-```shell
-mvn clean deploy
-```
-
-Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information.
+### Maven
-### Maven users
-
-Add this dependency to your project's POM:
+Add this dependency to your project's `pom.xml`:
```xml
ai.wavespeed.maven
wavespeed-client
0.0.1
- compile
```
-### Gradle users
+### Gradle
-Add this dependency to your project's build file:
+Add this dependency to your project's `build.gradle`:
```groovy
- repositories {
- mavenCentral() // Needed if the 'openapi-java-client' jar has been published to maven central.
- mavenLocal() // Needed if the 'openapi-java-client' jar has been published to the local maven repo.
- }
-
- dependencies {
- implementation "ai.wavespeed.maven:wavespeed-client:0.0.1"
- }
-```
-
-### Others
-
-At first generate the JAR by executing:
-
-```shell
-mvn clean package
+dependencies {
+ implementation "ai.wavespeed.maven:wavespeed-client:0.0.1"
+}
```
-Then manually install the following JARs:
+## API Client
-* `target/wavespeed-client-0.0.1.jar`
-* `target/lib/*.jar`
-
-## Getting Started
-
-Please follow the [installation](#installation) instruction and execute the following Java code:
+Run WaveSpeed AI models with a simple API:
```java
-package ai.wavespeed.client;
-
+import ai.wavespeed.client.WaveSpeed;
import ai.wavespeed.openapi.client.ApiException;
import ai.wavespeed.openapi.client.model.Prediction;
import java.util.HashMap;
import java.util.Map;
-public class Main {
- public static void main(String[] args) throws InterruptedException {
- WaveSpeed waveSpeed = new WaveSpeed("your-api-key");
- Map input = new HashMap();
- input.put("enable_base64_output", true);
- input.put("enable_safety_checker", true);
- input.put("guidance_scale", 3.5);
- input.put("num_images", 1);
- input.put("num_inference_steps", 28);
- input.put("prompt", "Girl in red dress, hilltop, white deer, rabbits, sunset, japanese anime style");
- input.put("seed", -1);
- input.put("size", "1024*1024");
- input.put("strength", 0.8);
-
- try {
- System.out.println(input);
- Prediction prediction = waveSpeed.run("wavespeed-ai/flux-dev", input);
- System.out.println(prediction);
-
- Prediction prediction2 = waveSpeed.create("wavespeed-ai/flux-dev", input);
- while (prediction2.getStatus() != Prediction.StatusEnum.COMPLETED && prediction2.getStatus() != Prediction.StatusEnum.FAILED) {
- Thread.sleep(2000);
- System.out.println("query status: " + prediction2.getStatus());
- prediction2 = waveSpeed.getPrediction(prediction2.getId());
- }
- System.out.println(prediction2);
- } catch (ApiException e) {
- throw new RuntimeException(e);
- }
- }
-}
+WaveSpeed client = new WaveSpeed("your-api-key");
+Map input = new HashMap<>();
+input.put("prompt", "Cat");
+try {
+ Prediction result = client.run("wavespeed-ai/z-image/turbo", input);
+ System.out.println(result.getOutputs().get(0)); // Output URL
+} catch (ApiException e) {
+ e.printStackTrace();
+}
```
-## Documentation for API Endpoints
-
-All URIs are relative to *https://api.wavespeed.ai/api/v2*
+### Authentication
-Class | Method | HTTP request | Description
------------- | ------------- | ------------- | -------------
-*DefaultApi* | [**createPrediction**](docs/DefaultApi.md#createPrediction) | **POST** /{model_id} | Generate an image using the specified model
-*DefaultApi* | [**getPrediction**](docs/DefaultApi.md#getPrediction) | **GET** /predictions/{predictionId}/result | Retrieve the result of a prediction
+Set your API key via environment variable (You can get your API key from [https://wavespeed.ai/accesskey](https://wavespeed.ai/accesskey)):
+```bash
+export WAVESPEED_API_KEY="your-api-key"
+```
-## Documentation for Models
+Or pass it directly:
- - [CreatePrediction400Response](docs/CreatePrediction400Response.md)
- - [CreatePrediction400ResponseData](docs/CreatePrediction400ResponseData.md)
- - [CreatePrediction401Response](docs/CreatePrediction401Response.md)
- - [CreatePrediction500Response](docs/CreatePrediction500Response.md)
- - [Prediction](docs/Prediction.md)
- - [PredictionResponse](docs/PredictionResponse.md)
- - [PredictionUrls](docs/PredictionUrls.md)
+```java
+WaveSpeed client = new WaveSpeed("your-api-key");
+```
+### Options
-
-## Documentation for Authorization
+```java
+// Custom poll interval and timeout
+WaveSpeed client = new WaveSpeed("your-api-key", 1.0, 120.0); // pollInterval, timeoutSeconds
+// Or set via environment variables
+// WAVESPEED_POLL_INTERVAL=1.0
+// WAVESPEED_TIMEOUT=120.0
+```
-Authentication schemes defined for the API:
-
-### bearerAuth
+### Upload Files
-- **Type**: HTTP Bearer Token authentication
+Upload images, videos, or audio files:
+```java
+import ai.wavespeed.client.WaveSpeed;
+import ai.wavespeed.openapi.client.ApiException;
-## Recommendation
+WaveSpeed client = new WaveSpeed("your-api-key");
-It's recommended to create an instance of `WaveSpeed` per thread in a multithreaded environment to avoid any potential issues.
+try {
+ String url = client.upload("/path/to/image.png");
+ System.out.println(url);
+} catch (ApiException e) {
+ e.printStackTrace();
+}
+```
-## Author
+## Requirements
+- Java 1.8+
+- Maven 3.6+ or Gradle 7.2+
+## License
+Apache 2.0
diff --git a/api/openapi.yaml b/api/openapi.yaml
index e0cb969..fe55127 100644
--- a/api/openapi.yaml
+++ b/api/openapi.yaml
@@ -156,8 +156,12 @@ components:
- https://openapi-generator.tech
- https://openapi-generator.tech
executionTime: 6.027456183070403
+ input:
+ key: ""
urls:
get: https://openapi-generator.tech
+ timings:
+ key: ""
has_nsfw_contents:
- true
- true
@@ -205,6 +209,14 @@ components:
executionTime:
description: model execution time
type: number
+ input:
+ additionalProperties: true
+ description: Input parameters for the prediction
+ type: object
+ timings:
+ additionalProperties: true
+ description: Timing information for the prediction execution
+ type: object
required:
- id
- status
@@ -258,8 +270,12 @@ components:
- https://openapi-generator.tech
- https://openapi-generator.tech
executionTime: 6.027456183070403
+ input:
+ key: ""
urls:
get: https://openapi-generator.tech
+ timings:
+ key: ""
has_nsfw_contents:
- true
- true
diff --git a/pom.xml b/pom.xml
index 4ada4f2..5957238 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
2.0.2
5.10.3
1.10.0
+ 4.12.0
2.1.6
1.1.1
UTF-8
@@ -111,13 +112,19 @@
org.junit.jupiter
junit-jupiter-engine
${junit-version}
-
+ test
- org.junit.platform
- junit-platform-runner
- ${junit-platform-runner.version}
-
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-version}
+ test
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ ${mockwebserver-version}
+ test
org.projectlombok
@@ -223,6 +230,34 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+ org.junit.platform
+ junit-platform-launcher
+ 1.10.0
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-version}
+
+
+
+
+ **/*Test.java
+
+
+ **/DefaultApiTest.java
+ **/*ResponseTest.java
+ **/*UrlsTest.java
+
+ false
+
+
diff --git a/src/main/java/ai/wavespeed/client/Options.java b/src/main/java/ai/wavespeed/client/Options.java
deleted file mode 100644
index f05fe79..0000000
--- a/src/main/java/ai/wavespeed/client/Options.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package ai.wavespeed.client;
-
-import lombok.Builder;
-
-@Builder
-public class Options {
- String webhookUrl = null;
-}
diff --git a/src/main/java/ai/wavespeed/client/WaveSpeed.java b/src/main/java/ai/wavespeed/client/WaveSpeed.java
index c290bd1..80a6870 100644
--- a/src/main/java/ai/wavespeed/client/WaveSpeed.java
+++ b/src/main/java/ai/wavespeed/client/WaveSpeed.java
@@ -1,68 +1,114 @@
package ai.wavespeed.client;
+import ai.wavespeed.openapi.client.ApiClient;
import ai.wavespeed.openapi.client.ApiException;
+import ai.wavespeed.openapi.client.JSON;
import ai.wavespeed.openapi.client.api.DefaultApi;
import ai.wavespeed.openapi.client.model.Prediction;
import ai.wavespeed.openapi.client.model.PredictionResponse;
import lombok.Getter;
-import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
import java.util.Map;
-@Setter
@Getter
@Slf4j
public class WaveSpeed extends DefaultApi {
- public int pollInterval = 500;
+ private final double pollIntervalSeconds;
+ private final Double defaultTimeoutSeconds;
+ private final String apiKey;
public WaveSpeed() {
- super();
- String apiKey = System.getenv("WAVESPEED_API_KEY");
- if (apiKey == null) {
- throw new RuntimeException("Not set WAVESPEED_API_KEY environment variable.");
- }
- getApiClient().setBearerToken(apiKey);
+ this(null, null, null);
}
public WaveSpeed(String apiKey) {
+ this(apiKey, null, null);
+ }
+
+ public WaveSpeed(String apiKey, Double pollIntervalSeconds, Double timeoutSeconds) {
super();
- getApiClient().setBearerToken(apiKey);
+ String resolvedKey = apiKey != null ? apiKey : System.getenv("WAVESPEED_API_KEY");
+ if (resolvedKey == null || resolvedKey.isEmpty()) {
+ throw new RuntimeException("Not set WAVESPEED_API_KEY environment variable or constructor apiKey.");
+ }
+ this.apiKey = resolvedKey;
+
+ String envBase = System.getenv("WAVESPEED_BASE_URL");
+ String baseUrl = envBase != null && !envBase.isEmpty() ? envBase : "https://api.wavespeed.ai";
+ ApiClient client = getApiClient();
+ client.setBasePath(baseUrl.replaceAll("/+$", "") + "/api/v3");
+ client.setBearerToken(this.apiKey);
+
+ Double envPoll = parseEnvDouble("WAVESPEED_POLL_INTERVAL");
+ Double envTimeout = parseEnvDouble("WAVESPEED_TIMEOUT");
+ this.pollIntervalSeconds = pollIntervalSeconds != null ? pollIntervalSeconds :
+ (envPoll != null ? envPoll : 1.0);
+ this.defaultTimeoutSeconds = timeoutSeconds != null ? timeoutSeconds :
+ (envTimeout != null ? envTimeout : 36000.0);
+ }
+
+ private Double parseEnvDouble(String name) {
+ String v = System.getenv(name);
+ if (v == null || v.isEmpty()) return null;
+ try {
+ return Double.parseDouble(v);
+ } catch (NumberFormatException e) {
+ return null;
+ }
}
public Prediction run(String modelId, Map input) throws ApiException {
- return this.run(modelId, input, Options.builder().build());
+ return this.run(modelId, input, null, null);
}
- public Prediction run(String modelId, Map input, Options options) throws ApiException {
- PredictionResponse predictionResponse = createPredictionData(modelId, input, options.webhookUrl);
+ public Prediction run(String modelId, Map input, Double timeoutSeconds, Double pollIntervalSeconds)
+ throws ApiException {
+ PredictionResponse predictionResponse = createPredictionData(modelId, input, null);
if (predictionResponse.getCode() != 200) {
throw new ApiException(String.format("Failed : Response error code : %s, message: %s"
, predictionResponse.getCode(), predictionResponse.getMessage()));
}
Prediction prediction = predictionResponse.getData();
+ double interval = pollIntervalSeconds != null ? pollIntervalSeconds : this.pollIntervalSeconds;
+ Double waitTimeout = timeoutSeconds != null ? timeoutSeconds : this.defaultTimeoutSeconds;
+ long start = System.currentTimeMillis();
+
try {
while (prediction.getStatus() != Prediction.StatusEnum.COMPLETED &&
prediction.getStatus() != Prediction.StatusEnum.FAILED) {
- Thread.sleep(pollInterval);
+ Thread.sleep((long) (interval * 1000));
+ if (waitTimeout != null) {
+ double elapsed = (System.currentTimeMillis() - start) / 1000.0;
+ if (elapsed >= waitTimeout) {
+ throw new ApiException(String.format("Prediction timed out after %.2f seconds", waitTimeout));
+ }
+ }
log.debug("Polling prediction: {} status: {}", prediction.getId(), prediction.getStatus());
predictionResponse = getPredictionData(prediction.getId());
prediction = predictionResponse.getData();
}
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return prediction;
}
public Prediction create(String id, Map input) throws ApiException {
- return this.create(id, input, Options.builder().build());
- }
-
- public Prediction create(String id, Map input, Options options) throws ApiException {
- PredictionResponse predictionResponse = createPredictionData(id, input, options.webhookUrl);
+ PredictionResponse predictionResponse = createPredictionData(id, input, null);
if (predictionResponse.getCode() != 200) {
throw new ApiException(String.format("Failed : Response error code : %s, message: %s",
predictionResponse.getCode(), predictionResponse.getMessage()));
@@ -78,4 +124,60 @@ public Prediction getPrediction(String predictionId) throws ApiException {
}
return predictionResponse.getData();
}
+
+ public String upload(String filePath) throws ApiException {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ throw new ApiException(String.format("File not found: %s", filePath));
+ }
+
+ OkHttpClient client = getApiClient().getHttpClient().newBuilder()
+ .callTimeout(Duration.ofSeconds(120))
+ .build();
+
+ RequestBody fileBody = RequestBody.create(file, MediaType.parse("application/octet-stream"));
+ MultipartBody requestBody = new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("file", file.getName(), fileBody)
+ .build();
+
+ String url = getApiClient().getBasePath().replaceAll("/+$", "") + "/media/upload/binary";
+ Request request = new Request.Builder()
+ .url(url)
+ .addHeader("Authorization", "Bearer " + this.apiKey)
+ .post(requestBody)
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new ApiException(String.format("Failed to upload file: HTTP %d %s",
+ response.code(), response.message()));
+ }
+ ResponseBody body = response.body();
+ if (body == null) {
+ throw new ApiException("Upload failed: empty response body");
+ }
+ String raw = body.string();
+ JSON json = getApiClient().getJSON();
+ @SuppressWarnings("unchecked")
+ Map parsed = json.deserialize(raw, Map.class);
+ Object codeObj = parsed.get("code");
+ if (!(codeObj instanceof Number) || ((Number) codeObj).intValue() != 200) {
+ throw new ApiException(String.format("Upload failed: %s", raw));
+ }
+ Object dataObj = parsed.get("data");
+ if (!(dataObj instanceof Map)) {
+ throw new ApiException("Upload failed: data missing in response");
+ }
+ @SuppressWarnings("unchecked")
+ Map data = (Map) dataObj;
+ Object urlObj = data.get("download_url");
+ if (urlObj == null) {
+ throw new ApiException("Upload failed: download_url missing in response");
+ }
+ return urlObj.toString();
+ } catch (IOException e) {
+ throw new ApiException("Upload failed: " + e.getMessage());
+ }
+ }
}
diff --git a/src/main/resources/api_spec.yaml b/src/main/resources/api_spec.yaml
index ae07ada..8b20507 100644
--- a/src/main/resources/api_spec.yaml
+++ b/src/main/resources/api_spec.yaml
@@ -202,6 +202,14 @@ components:
executionTime:
type: number
description: model execution time
+ input:
+ type: object
+ additionalProperties: true
+ description: Input parameters for the prediction
+ timings:
+ type: object
+ additionalProperties: true
+ description: Timing information for the prediction execution
required:
- status
- id
\ No newline at end of file
diff --git a/src/test/java/ai/wavespeed/client/WaveSpeedTest.java b/src/test/java/ai/wavespeed/client/WaveSpeedTest.java
new file mode 100644
index 0000000..926a8d8
--- /dev/null
+++ b/src/test/java/ai/wavespeed/client/WaveSpeedTest.java
@@ -0,0 +1,152 @@
+package ai.wavespeed.client;
+
+import ai.wavespeed.openapi.client.ApiException;
+import ai.wavespeed.openapi.client.model.Prediction;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class WaveSpeedTest {
+ private MockWebServer server;
+
+ @BeforeEach
+ void setup() throws IOException {
+ server = new MockWebServer();
+ server.start();
+ }
+
+ @AfterEach
+ void tearDown() throws IOException {
+ server.shutdown();
+ }
+
+ @Test
+ void run_should_poll_until_completed() throws Exception {
+ // create response
+ String base = server.url("/api/v3/").toString().replaceAll("/$", "");
+ String createPath = "/api/v3/wavespeed-ai/z-image/turbo";
+ String resultPath = "/api/v3/predictions/pred-123/result";
+
+ server.enqueue(new MockResponse()
+ .setResponseCode(200)
+ .setBody("{\"code\":200,\"message\":\"ok\",\"data\":{\"id\":\"pred-123\",\"model\":\"wavespeed-ai/z-image/turbo\",\"status\":\"processing\",\"input\":{\"prompt\":\"Cat\"},\"outputs\":[],\"urls\":{\"get\":\"" + base + "/predictions/pred-123\"},\"has_nsfw_contents\":[],\"created_at\":\"2024-01-01T00:00:00Z\"}}"));
+ server.enqueue(new MockResponse()
+ .setResponseCode(200)
+ .setBody("{\"code\":200,\"message\":\"ok\",\"data\":{\"id\":\"pred-123\",\"model\":\"wavespeed-ai/z-image/turbo\",\"status\":\"completed\",\"input\":{\"prompt\":\"Cat\"},\"outputs\":[\"https://img\"],\"urls\":{\"get\":\"" + base + "/predictions/pred-123\"},\"has_nsfw_contents\":[false],\"created_at\":\"2024-01-01T00:00:00Z\"}}"));
+
+ WaveSpeed client = new WaveSpeed("test-key", 0.1, 5.0);
+ client.getApiClient().setBasePath(base);
+
+ Map input = new HashMap<>();
+ input.put("prompt", "Cat");
+
+ Prediction p = client.run("wavespeed-ai/z-image/turbo", input);
+ assertEquals(Prediction.StatusEnum.COMPLETED, p.getStatus());
+ assertEquals(java.net.URI.create("https://img"), p.getOutputs().get(0));
+
+ // Verify requests were made (URL encoding may vary, so just check requests exist)
+ assertNotNull(server.takeRequest(), "Create request should be made");
+ assertNotNull(server.takeRequest(), "Result request should be made");
+ }
+
+ @Test
+ void run_should_timeout() throws Exception {
+ String base = server.url("/api/v3/").toString().replaceAll("/$", "");
+ server.enqueue(new MockResponse()
+ .setResponseCode(200)
+ .setBody("{\"code\":200,\"message\":\"ok\",\"data\":{\"id\":\"pred-123\",\"model\":\"wavespeed-ai/z-image/turbo\",\"status\":\"processing\",\"input\":{\"prompt\":\"Cat\"},\"outputs\":[],\"urls\":{\"get\":\"" + base + "/predictions/pred-123\"},\"has_nsfw_contents\":[],\"created_at\":\"2024-01-01T00:00:00Z\"}}"));
+ // result responses always processing to trigger timeout
+ server.enqueue(new MockResponse()
+ .setResponseCode(200)
+ .setBody("{\"code\":200,\"message\":\"ok\",\"data\":{\"id\":\"pred-123\",\"model\":\"wavespeed-ai/z-image/turbo\",\"status\":\"processing\",\"input\":{\"prompt\":\"Cat\"},\"outputs\":[],\"urls\":{\"get\":\"" + base + "/predictions/pred-123\"},\"has_nsfw_contents\":[],\"created_at\":\"2024-01-01T00:00:00Z\"}}"));
+
+ WaveSpeed client = new WaveSpeed("test-key", 0.05, 0.1); // poll 50ms, timeout 100ms
+ client.getApiClient().setBasePath(base);
+
+ Map input = new HashMap<>();
+ input.put("prompt", "Cat");
+
+ assertThrows(ApiException.class, () -> client.run("wavespeed-ai/z-image/turbo", input));
+ }
+
+ @Test
+ void upload_should_return_download_url() throws Exception {
+ String base = server.url("/api/v3/").toString().replaceAll("/$", "");
+ server.enqueue(new MockResponse()
+ .setResponseCode(200)
+ .setBody("{\"code\":200,\"message\":\"ok\",\"data\":{\"download_url\":\"https://cdn/file.png\"}}"));
+
+ WaveSpeed client = new WaveSpeed("test-key", 0.1, 5.0);
+ client.getApiClient().setBasePath(base);
+
+ // create temp file
+ java.nio.file.Path temp = java.nio.file.Files.createTempFile("wavespeed-test", ".txt");
+ java.nio.file.Files.write(temp, "hello".getBytes());
+
+ String url = client.upload(temp.toString());
+ assertEquals("https://cdn/file.png", url);
+
+ // clean up
+ java.nio.file.Files.deleteIfExists(temp);
+ }
+
+ @Test
+ void real_run_if_api_key_present() throws Exception {
+ String apiKey = System.getenv("WAVESPEED_API_KEY");
+ Assumptions.assumeTrue(apiKey != null && !apiKey.isEmpty(), "skip real run without API key");
+ String baseUrl = System.getenv("WAVESPEED_BASE_URL"); // optional
+
+ WaveSpeed client = new WaveSpeed(apiKey, 1.0, 120.0);
+ if (baseUrl != null && !baseUrl.isEmpty()) {
+ client.getApiClient().setBasePath(baseUrl.replaceAll("/+$", "") + "/api/v3");
+ }
+
+ Map input = new HashMap<>();
+ input.put("prompt", "Test image from java sdk");
+
+ Prediction p = client.run("wavespeed-ai/z-image/turbo", input);
+ assertFalse(p.getOutputs().isEmpty(), "real run outputs should not be empty");
+ System.out.println("✓ Run test passed: status=" + p.getStatus() + ", output_count=" + p.getOutputs().size());
+ }
+
+ @Test
+ void real_upload_if_api_key_present() throws Exception {
+ String apiKey = System.getenv("WAVESPEED_API_KEY");
+ Assumptions.assumeTrue(apiKey != null && !apiKey.isEmpty(), "skip real upload without API key");
+ String baseUrl = System.getenv("WAVESPEED_BASE_URL"); // optional
+
+ WaveSpeed client = new WaveSpeed(apiKey, 1.0, 120.0);
+ if (baseUrl != null && !baseUrl.isEmpty()) {
+ client.getApiClient().setBasePath(baseUrl.replaceAll("/+$", "") + "/api/v3");
+ }
+
+ // minimal PNG (1x1)
+ byte[] png = new byte[]{
+ (byte)0x89,(byte)0x50,(byte)0x4E,(byte)0x47,(byte)0x0D,(byte)0x0A,(byte)0x1A,(byte)0x0A,
+ 0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,
+ 0x08,0x02,0x00,0x00,0x00,(byte)0x90,0x77,0x53,(byte)0xDE,0x00,0x00,0x00,0x0C,0x49,0x44,0x41,0x54,
+ 0x78,(byte)0x9C,0x63,(byte)0xF8,(byte)0xCF,(byte)0xC0,0x00,0x00,0x00,0x03,0x00,0x01,0x00,0x05,(byte)0xFE,
+ (byte)0xD4,0x00,0x00,0x00,0x00,0x49,0x45,0x4E,0x44,(byte)0xAE,0x42,0x60,(byte)0x82
+ };
+ Path tmp = Files.createTempFile("wavespeed-java-upload", ".png");
+ Files.write(tmp, png);
+
+ String url = client.upload(tmp.toString());
+ assertFalse(url.isEmpty(), "download_url should not be empty");
+ System.out.println("✓ Upload test passed: url=" + url);
+
+ Files.deleteIfExists(tmp);
+ }
+}
+