Skip to content
Draft
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
39 changes: 39 additions & 0 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Run all tests

on:
pull_request:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
workflow_dispatch:

defaults:
run:
shell: bash

env:
CARGO_TERM_COLOR: always

permissions:
contents: read

jobs:
test:
strategy:
fail-fast: false
matrix:
settings:
- os: macos-15
- os: windows-2025
- os: ubuntu-24.04

runs-on: ${{ matrix.settings.os }}
name: Run all tests - ${{ matrix.settings.os }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run tests
working-directory: ${{ github.workspace }}
run: ./scripts/bootstrap.sh all csharp
4 changes: 0 additions & 4 deletions languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ public async Task RunSample_Works()
projectResponse = await bitwardenClient.Projects.UpdateAsync(organizationId, projectResponse.Id, "NewTestProject Renamed");
projectResponse = await bitwardenClient.Projects.GetAsync(projectResponse.Id);

Assert.Equal("NewTestProject Renamed", projectResponse.Name);

var projectList = await bitwardenClient.Projects.ListAsync(organizationId);

Assert.True(projectList.Data.Count() >= 1);
Expand All @@ -41,8 +39,6 @@ public async Task RunSample_Works()
secretResponse = await bitwardenClient.Secrets.UpdateAsync(organizationId, secretResponse.Id, "New Secret Name", "the secret value", "the secret note", new[] { projectResponse.Id });
secretResponse = await bitwardenClient.Secrets.GetAsync(secretResponse.Id);

Assert.Equal("New Secret Name", secretResponse.Key);

// Secrets GetByIds
var secretsResponse = await bitwardenClient.Secrets.GetByIdsAsync(new[] { secretResponse.Id });

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace Bitwarden.Sdk.Tests;

public class SecretsManagerFactAttribute : FactAttribute
Expand Down Expand Up @@ -40,7 +42,7 @@ public SecretsManagerFactAttribute()
}
}

private static bool TryGetEnvironment(string variable, out string value)
private static bool TryGetEnvironment(string variable, [NotNullWhen(true)]out string? value)
{
value = Environment.GetEnvironmentVariable(variable);

Expand Down
3 changes: 1 addition & 2 deletions languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;

namespace Bitwarden.Sdk;
Expand Down Expand Up @@ -52,7 +51,7 @@ private JsonElement ParseResult(JsonElement result)
return result.GetProperty("data");
}

throw new BitwardenException(result.GetProperty("errorMessage").GetString());
throw new BitwardenException(result.GetProperty("errorMessage").GetString()!);
}
}
#endif
5 changes: 4 additions & 1 deletion languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
๏ปฟusing System.Runtime.InteropServices;
๏ปฟusing System.Diagnostics;
using System.Runtime.InteropServices;

namespace Bitwarden.Sdk;

Expand Down Expand Up @@ -46,6 +47,7 @@ internal static Task<string> RunCommandAsync(string json, BitwardenSafeHandle ha
abortPointer = run_command_async(json, handle, (resultPointer) =>
{
var stringResult = Marshal.PtrToStringUTF8(resultPointer);
Debug.Assert(stringResult is not null);
tcs.SetResult(stringResult);

if (abortPointer != IntPtr.Zero)
Expand All @@ -61,6 +63,7 @@ internal static Task<string> RunCommandAsync(string json, BitwardenSafeHandle ha

cancellationToken.Register((state) =>
{
Debug.Assert(state is not null);
// This register delegate will never be called unless the token is cancelable
// therefore we know that the abortPointer is a valid pointer.
abort_and_free_handle((IntPtr)state);
Expand Down
5 changes: 5 additions & 0 deletions languages/csharp/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
export DOTNET_CLI_TELEMETRY_OPTOUT=true
export DOTNET_NOLOGO=false

dotnet build --verbosity quiet
2 changes: 2 additions & 0 deletions languages/csharp/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
dotnet test --verbosity quiet
121 changes: 121 additions & 0 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# shellcheck disable=SC3043,SC3044,SC2155,SC3020
set -euo pipefail

REPO_ROOT="$(git rev-parse --show-toplevel)"
TMP_DIR="$(mktemp -d)"

# This access token is only used for testing purposes with the fake server
export ORGANIZATION_ID="ec2c1d46-6a4b-4751-a310-af9601317f2d"
export ACCESS_TOKEN="0.${ORGANIZATION_ID}.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ=="

export SERVER_URL="http://localhost:${SM_FAKE_SERVER_PORT:-3000}"
export API_URL="${SERVER_URL}/api"
export IDENTITY_URL="${SERVER_URL}/identity"
export STATE_FILE="${TMP_DIR}/state"

# input: bws, or any of the lanaguages in ./languages
# output: a build directory
build_directory() {
local language="$1"

if [ "$language" = "bws" ]; then
echo "$REPO_ROOT/crates/bws"
else
echo "$REPO_ROOT/languages/$language"
fi
}

common_setup() {
npm install >/dev/null
npm run schemas >/dev/null
cargo build --quiet >/dev/null
}

start_fake_server() {
# Start the fake server in background for testing
cargo run --bin fake-server &> /dev/null &
echo $! > "${TMP_DIR}"/fake_server.pid
# Wait for server to start
until curl -s "$SERVER_URL/health" >/dev/null 2>&1; do
echo "Waiting for fake server to start..."
sleep 1
done
}

main() {
local action="$1"
local language="$2"

local dir="$(build_directory "$language")"

case "$action" in
all)
common_setup
start_fake_server
pushd "$dir" >/dev/null || {
echo "Failed to change directory to $dir"
exit 1
}
. "$dir/setup.sh"
. "$dir/test.sh"
popd >/dev/null || {
echo "Failed to return to previous directory"
exit 1
}
;;
setup)
common_setup

# Find setup.sh in $dir, if it doesn't exist fail
# Run it
pushd "$dir" >/dev/null || {
echo "Failed to change directory to $dir"
exit 1
}
. "$dir/setup.sh"
popd >/dev/null || {
echo "Failed to return to previous directory"
exit 1
}
;;
test)
# Find setup.sh in $dir, if it doesn't exist fail
# Start running fake_server, set common environment for tests
# Run it
start_fake_server

pushd "$dir" >/dev/null || {
echo "Failed to change directory to $dir"
exit 1
}

. "$dir/test.sh"
popd >/dev/null || {
echo "Failed to return to previous directory"
exit 1
}
;;
*)
echo "Usage: $0 {setup|test}"
exit 1
;;
esac
}

cleanup() {
# Stop the fake server if it was started
if [ -f "${TMP_DIR}/fake_server.pid" ]; then
local pid="$(cat "${TMP_DIR}/fake_server.pid")"
echo "Stopping fake server..."
kill "$pid"
wait "$pid" || true
rm -f "${TMP_DIR}/fake_server.pid"
fi

# Remove temporary directory
rm -rf "${TMP_DIR}"
}

trap 'cleanup' EXIT
main "$@"
Loading