Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .sdkharness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SDK harness contract

`tests.tsv` is the versioned contract read by Backblaze's centralized SDK quality harness
(`sdkharness` in `backblaze-labs/demand-side-ai`). This repository owns the executable
assertions in `tests/`; the harness builds this checkout, starts a fresh local B2 simulator,
and retains and reports the evidence.

One scenario so far: `health/golden-path`, the customer golden path. `tests/run-health` runs
`tests/health/GoldenPath.java` as a single-file Java program that authorizes, uploads a small
file, downloads it and compares the bytes, lists it, deletes it and confirms it is gone, then
prints one result line:

```text
SDKHARNESS_RESULT health golden-path PASS -
```

## What the check can and cannot reach

- **Only the loopback simulator.** `run-health` refuses any `SDKHARNESS_SIMULATOR_URL` that
is not `http://127.0.0.1:<port>`. The SDK's HTTP support for test servers
(`HttpClientFactoryImpl.Builder.setSupportInsecureHttp`) is enabled for that reason only.
- **Only the simulator's fixed test credential** (`test-key-id` / `test-key`, in the source).
Every ambient `B2_*` variable except `B2_BUCKET_NAME` is dropped first.
- **No build.** The harness builds the `core` and `httpclient` jars from this checkout and
passes them, with their runtime dependencies, as `SDKHARNESS_JAVA_CLASSPATH`. The build
files here are not changed. A missing `java` on `PATH` reports `SKIP`, which the harness
counts as a failure.

## Run it by hand

Requires JDK 11 or later and a simulator (`backblaze-labs/b2-simulator`) with a bucket named
`sdkharness-healthcheck`:

```bash
./gradlew :b2-sdk-httpclient:jar
SDKHARNESS_TEST_LEVEL=health SDKHARNESS_SCENARIO=golden-path \
SDKHARNESS_SIMULATOR_URL=http://127.0.0.1:<port> B2_BUCKET_NAME=sdkharness-healthcheck \
SDKHARNESS_JAVA_CLASSPATH=<httpclient jar>:<core jar>:<httpclient, httpcore, commons-logging, commons-codec jars> \
.sdkharness/tests/run-health
```
2 changes: 2 additions & 0 deletions .sdkharness/tests.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test_level scenario target executable
health golden-path simulator ./.sdkharness/tests/run-health
101 changes: 101 additions & 0 deletions .sdkharness/tests/health/GoldenPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2026, Backblaze Inc. All Rights Reserved.
// License https://www.backblaze.com/using_b2_code.html

import com.backblaze.b2.client.B2ClientConfig;
import com.backblaze.b2.client.B2StorageClient;
import com.backblaze.b2.client.contentHandlers.B2ContentMemoryWriter;
import com.backblaze.b2.client.contentSources.B2ByteArrayContentSource;
import com.backblaze.b2.client.contentSources.B2ContentTypes;
import com.backblaze.b2.client.structures.B2Bucket;
import com.backblaze.b2.client.structures.B2FileVersion;
import com.backblaze.b2.client.structures.B2ListFileNamesRequest;
import com.backblaze.b2.client.structures.B2ListFileVersionsRequest;
import com.backblaze.b2.client.structures.B2UploadFileRequest;
import com.backblaze.b2.client.webApiHttpClient.B2StorageHttpClientBuilder;
import com.backblaze.b2.client.webApiHttpClient.HttpClientFactoryImpl;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.UUID;

/**
* Customer golden path against the sdkharness simulator: authorize, upload, byte-verified
* download, list, delete, and confirm the file is gone. Run by ../run-health, which checks
* the simulator URL and supplies the classpath. Prints exactly one verdict line:
* {@code GOLDEN-PATH PASS} or {@code GOLDEN-PATH FAIL <step>: <detail>}.
*/
public class GoldenPath {
// The simulator's fixed test credential. No other credential is ever used.
private static final String KEY_ID = "test-key-id";
private static final String KEY = "test-key";

private static String step = "configuration";

public static void main(String[] args) {
final String masterUrl = System.getenv("SDKHARNESS_SIMULATOR_URL");
final String bucketName = System.getenv("B2_BUCKET_NAME");
if (masterUrl == null || bucketName == null || bucketName.isEmpty()) {
fail("simulator URL or bucket name is missing");
}
final B2ClientConfig config = B2ClientConfig.builder(KEY_ID, KEY, "b2-sdk-java-sdkharness-health")
.setMasterUrl(masterUrl)
.build();
final String name = "sdkharness-health-check/" + UUID.randomUUID() + ".txt";
final byte[] payload = ("b2-sdk-java health check " + name).getBytes(StandardCharsets.UTF_8);

// The simulator speaks plain HTTP on loopback (run-health refuses any other URL), so this
// enables the SDK's documented test-environment switch; everything else is the default client.
try (B2StorageClient client = B2StorageHttpClientBuilder.builder(config)
.setHttpClientFactory(HttpClientFactoryImpl.builder().setSupportInsecureHttp(true).build())
.build()) {
step = "authorize";
client.getAccountAuthorization();

step = "bucket";
final B2Bucket bucket = client.getBucketOrNullByName(bucketName);
if (bucket == null) {
fail("bucket " + bucketName + " not found");
}
final String bucketId = bucket.getBucketId();

step = "upload";
final B2FileVersion uploaded = client.uploadSmallFile(B2UploadFileRequest
.builder(bucketId, name, B2ContentTypes.TEXT_PLAIN, B2ByteArrayContentSource.build(payload))
.build());

step = "download";
final B2ContentMemoryWriter sink = B2ContentMemoryWriter.build();
client.downloadById(uploaded.getFileId(), sink);
if (!Arrays.equals(sink.getBytes(), payload)) {
fail("downloaded bytes differ from the uploaded bytes");
}

step = "list";
boolean listed = false;
for (B2FileVersion version : client.fileNames(B2ListFileNamesRequest.builder(bucketId).setPrefix(name).build())) {
listed |= name.equals(version.getFileName()) && uploaded.getFileId().equals(version.getFileId());
}
if (!listed) {
fail("the uploaded file is not listed");
}

step = "delete";
client.deleteFileVersion(uploaded);

step = "absence";
for (B2FileVersion version : client.fileVersions(B2ListFileVersionsRequest.builder(bucketId).setPrefix(name).build())) {
if (name.equals(version.getFileName())) {
fail("a version is still listed after delete");
}
}
} catch (Exception e) {
fail(e.getClass().getSimpleName() + " " + e.getMessage());
}
System.out.println("GOLDEN-PATH PASS");
}

private static void fail(String detail) {
System.out.println("GOLDEN-PATH FAIL " + step + ": " + String.valueOf(detail).replaceAll("\\s+", " "));
System.exit(1);
}
}
40 changes: 40 additions & 0 deletions .sdkharness/tests/run-health
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Copyright 2026, Backblaze Inc. All Rights Reserved.
# License https://www.backblaze.com/using_b2_code.html
#
# sdkharness customer health (golden path) for this checkout. See ../README.md.
set -u

LEVEL=health
SCENARIO=golden-path
HERE="$(cd "$(dirname "$0")" && pwd -P)"
result() { printf 'SDKHARNESS_RESULT\t%s\t%s\t%s\t%s\n' "$LEVEL" "$SCENARIO" "$1" "$2"; }
fail() { result FAIL "$1"; exit 1; }

[ "${SDKHARNESS_TEST_LEVEL:-}" = "$LEVEL" ] && [ "${SDKHARNESS_SCENARIO:-}" = "$SCENARIO" ] \
|| fail 'configuration: unexpected contract identity'
[[ "${SDKHARNESS_SIMULATOR_URL:-}" =~ ^http://127\.0\.0\.1:[0-9]{1,5}/?$ ]] \
|| fail 'configuration: simulator URL must be an IPv4 loopback HTTP origin'
[ -n "${B2_BUCKET_NAME:-}" ] || fail 'configuration: B2_BUCKET_NAME is unset'
[ -n "${SDKHARNESS_JAVA_CLASSPATH:-}" ] \
|| fail 'configuration: SDKHARNESS_JAVA_CLASSPATH is unset (the core and httpclient jars built from this checkout, plus their runtime dependencies)'
command -v java >/dev/null 2>&1 || { result SKIP 'missing-runtime: no java on PATH'; exit 0; }

# Only the simulator's fixed credential (in GoldenPath.java) reaches the SDK: every ambient B2_*
# is dropped. The bucket name is kept; it is a name, not a secret, and the harness creates it.
bucket="$B2_BUCKET_NAME"
for name in $(compgen -e); do case "$name" in B2_*) unset "$name" ;; esac; done
export B2_BUCKET_NAME="$bucket"

log="$(mktemp "${TMPDIR:-/tmp}/sdkharness-java-health.XXXXXX")" || fail 'setup: could not create a log file'
trap 'rm -f "$log"' EXIT
if java -cp "$SDKHARNESS_JAVA_CLASSPATH" "$HERE/health/GoldenPath.java" >"$log" 2>&1; then rc=0; else rc=$?; fi
grep -v '^GOLDEN-PATH ' "$log" || true
verdicts="$(grep -c '^GOLDEN-PATH ' "$log" || true)"
[ "$verdicts" = 1 ] || fail "contract: expected one verdict, got $verdicts (exit $rc)"
verdict="$(grep '^GOLDEN-PATH ' "$log")"
case "$verdict" in
'GOLDEN-PATH PASS') [ "$rc" = 0 ] || fail "contract: PASS exited $rc"; result PASS - ;;
'GOLDEN-PATH FAIL '*) fail "${verdict#GOLDEN-PATH FAIL }" ;;
*) fail 'contract: malformed verdict' ;;
esac
Loading