Skip to content

Commit 4950ebe

Browse files
feat: go test suite (#1289)
## 🎟️ Tracking https://bitwarden.atlassian.net/browse/SM-1512 ## 📔 Objective <!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. --> ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## 🦮 Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - 👍 (`:+1:`) or similar for great changes - 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info - ❓ (`:question:`) for questions - 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - 🎨 (`:art:`) for suggestions / improvements - ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention - 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt - ⛏ (`:pick:`) for minor or nitpick changes
1 parent 628dbf0 commit 4950ebe

File tree

7 files changed

+271
-4
lines changed

7 files changed

+271
-4
lines changed

.github/workflows/build-go.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,3 @@ jobs:
4646
- name: Build
4747
working-directory: languages/go
4848
run: go build -v ./...
49-
50-
- name: Test
51-
working-directory: languages/go
52-
run: go test -v ./...

.github/workflows/test-go.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Test Go SDK
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
- "rc"
8+
- "hotfix-rc"
9+
paths:
10+
- "languages/python/**"
11+
- "crates/bitwarden/**"
12+
- "crates/bitwarden-c/**"
13+
- "crates/fake-server/**"
14+
- ".github/workflows/test-go.yml"
15+
pull_request:
16+
types: [opened, synchronize]
17+
paths:
18+
- "languages/go/**"
19+
- "crates/bitwarden/**"
20+
- "crates/bitwarden-c/**"
21+
- "crates/fake-server/**"
22+
- ".github/workflows/test-go.yml"
23+
24+
permissions:
25+
contents: read
26+
27+
defaults:
28+
run:
29+
shell: bash
30+
31+
jobs:
32+
test:
33+
name: Test Go SDK
34+
runs-on: ${{ matrix.os }}
35+
strategy:
36+
matrix:
37+
os:
38+
- ubuntu-latest
39+
- macos-latest
40+
# - windows-latest FIXME: https://gist.github.com/tangowithfoxtrot/beb737c1a804533870f10560eaf2f7c3
41+
42+
steps:
43+
- name: Checkout repo
44+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
45+
46+
- name: Set up Go
47+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
48+
with:
49+
go-version: "1.20"
50+
cache: "true"
51+
52+
- name: Set up Node.js
53+
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
54+
with:
55+
node-version: "18"
56+
cache: "npm"
57+
58+
- name: Set up Rust
59+
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable
60+
with:
61+
toolchain: stable
62+
63+
- name: Cache Rust dependencies
64+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
65+
with:
66+
path: |
67+
~/.cargo/registry
68+
~/.cargo/git
69+
target
70+
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
71+
restore-keys: |
72+
${{ runner.os }}-cargo-
73+
74+
- name: Setup Go SDK
75+
run: ./scripts/bootstrap.sh setup go
76+
77+
- name: Run Go SDK tests
78+
run: ./scripts/bootstrap.sh test go
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package sdk
2+
3+
import (
4+
"os"
5+
"testing"
6+
"time"
7+
8+
"github.com/gofrs/uuid"
9+
)
10+
11+
func TestBitwardenClient(t *testing.T) {
12+
apiURL := os.Getenv("API_URL")
13+
identityURL := os.Getenv("IDENTITY_URL")
14+
accessToken := os.Getenv("ACCESS_TOKEN")
15+
organizationIDStr := os.Getenv("ORGANIZATION_ID")
16+
stateFile := os.Getenv("STATE_FILE")
17+
18+
bitwardenClient, err := NewBitwardenClient(&apiURL, &identityURL)
19+
if err != nil {
20+
t.Errorf("Failed to create Bitwarden client: %v", err)
21+
}
22+
defer bitwardenClient.Close()
23+
24+
err = bitwardenClient.AccessTokenLogin(accessToken, &stateFile)
25+
if err != nil {
26+
t.Errorf("AccessTokenLogin failed: %v", err)
27+
}
28+
29+
organizationID, err := uuid.FromString(organizationIDStr)
30+
if err != nil {
31+
t.Errorf("Failed to parse organization ID: %v", err)
32+
}
33+
34+
// --- generator ---
35+
request := PasswordGeneratorRequest{
36+
AvoidAmbiguous: true,
37+
Length: 32,
38+
Lowercase: true,
39+
MinLowercase: ptr(int64(2)),
40+
MinNumber: ptr(int64(2)),
41+
MinSpecial: ptr(int64(2)),
42+
MinUppercase: ptr(int64(2)),
43+
Numbers: true,
44+
Special: true,
45+
Uppercase: true,
46+
}
47+
48+
password, err := bitwardenClient.Generators().GeneratePassword(request)
49+
if err != nil || len(*password) != 32 {
50+
t.Errorf("generate failed: %v", err)
51+
}
52+
53+
// --- secrets ---
54+
// list; should return a list of secret IDs (without the values)
55+
secretList, err := bitwardenClient.Secrets().List(organizationID.String())
56+
if err != nil || len(secretList.Data) == 0 {
57+
t.Errorf("secret list failed: %v", err)
58+
t.Errorf("secret list data: %v", secretList.Data)
59+
}
60+
61+
// get; should return a secret whose key is "btw" (from the fake-server)
62+
secret, err := bitwardenClient.Secrets().Get(secretList.Data[0].ID)
63+
hardCodedSecretKey := "btw" // embedded in the fake-server
64+
if err != nil || secret.Key != hardCodedSecretKey {
65+
t.Errorf("secret get failed: %v", err)
66+
t.Errorf("secret key: %s", secret.Key)
67+
}
68+
69+
// getByIds; should return secret data for the given IDs
70+
secretIDs := []string{uuid.Must(uuid.NewV4()).String(), uuid.Must(uuid.NewV4()).String()}
71+
hardCodedSecretKey = "FERRIS" // embedded in the fake-server
72+
secrets, err := bitwardenClient.Secrets().GetByIDS(secretIDs)
73+
if err != nil || secrets.Data[0].Key != hardCodedSecretKey {
74+
t.Errorf("secret getByIds failed: %v", err)
75+
t.Errorf("secret key: %s", secrets.Data[0].Key)
76+
}
77+
78+
// create; should return a secret with the given key, value, and note
79+
newProjectID, _ := uuid.NewV4() // random project ID is fine; the fake-server doesn't validate it
80+
81+
secret, err = bitwardenClient.Secrets().Create("testKey", "testValue", "testNote", organizationID.String(), []string{newProjectID.String()})
82+
if err != nil || secret.Key != "testKey" || secret.Value != "testValue" || secret.Note != "testNote" {
83+
t.Errorf("secret create failed: %v", err)
84+
}
85+
86+
// update; should return a secret with the updated key, value, and note
87+
updatedSecret, err := bitwardenClient.Secrets().Update(secret.ID, "updatedKey", "updatedValue", "updatedNote", organizationID.String(), []string{})
88+
if err != nil || updatedSecret.Key != "updatedKey" || updatedSecret.Value != "updatedValue" || updatedSecret.Note != "updatedNote" || updatedSecret.ProjectID != nil {
89+
t.Errorf("secret update failed: %v", err)
90+
}
91+
92+
// delete; should delete the secret and return an empty response
93+
res, err := bitwardenClient.Secrets().Delete([]string{
94+
uuid.Must(uuid.NewV4()).String(),
95+
})
96+
if err != nil {
97+
t.Errorf("secret delete failed: %v", err)
98+
t.Errorf("expected nil response, got: %v", res)
99+
}
100+
101+
// sync; should return new/modified secrets from a given point in time
102+
syncedSecrets, err := bitwardenClient.Secrets().Sync(organizationID.String(), nil)
103+
if err != nil || syncedSecrets.HasChanges == false {
104+
t.Errorf("secret initial sync failed: %v", err)
105+
t.Errorf("secret hasChanges: %v", syncedSecrets.HasChanges)
106+
}
107+
108+
lastSyncTime := time.Now()
109+
newSyncedSecrets, err := bitwardenClient.Secrets().Sync(organizationID.String(), &lastSyncTime)
110+
if err != nil || newSyncedSecrets.HasChanges == true {
111+
t.Errorf("secret sync with lastSyncTime failed: %v", err)
112+
t.Errorf("secret hasChanges: %v", newSyncedSecrets.HasChanges)
113+
}
114+
115+
// --- projects ---
116+
// list; should return a list of project IDs
117+
projectList, err := bitwardenClient.Projects().List(organizationID.String())
118+
if err != nil || len(projectList.Data) == 0 {
119+
t.Errorf("project list failed: %v", err)
120+
t.Errorf("project list data: %v", projectList.Data)
121+
}
122+
123+
// get; should return a project with the given ID
124+
_, err = bitwardenClient.Projects().Get(projectList.Data[0].ID)
125+
if err != nil {
126+
t.Errorf("project get failed: %v", err)
127+
}
128+
129+
// create; should return a project with the given name
130+
project, err := bitwardenClient.Projects().Create(organizationID.String(), "testProject")
131+
if err != nil || project.Name != "testProject" {
132+
t.Errorf("project create failed: %v", err)
133+
t.Errorf("expected project name: testProject, got: %s", project.Name)
134+
}
135+
136+
// update; should return a project with the updated name
137+
project, err = bitwardenClient.Projects().Update(project.ID, organizationID.String(), "updatedProject")
138+
if err != nil || project.Name != "updatedProject" {
139+
t.Errorf("project update failed: %v", err)
140+
t.Errorf("expected project name: updatedProject, got: %s", project.Name)
141+
}
142+
143+
// delete; should delete the project
144+
projectRes, err := bitwardenClient.Projects().Delete([]string{project.ID})
145+
if err != nil {
146+
t.Errorf("project delete failed: %v", err)
147+
t.Errorf("expected deleted project ID: %s, got: %v", project.ID, projectRes.Data)
148+
}
149+
}
150+
151+
// Helper functions
152+
func ptr(i int64) *int64 {
153+
return &i
154+
}

languages/go/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module github.com/bitwarden/sdk-go
22

33
go 1.21
4+
5+
require github.com/gofrs/uuid v4.4.0+incompatible
6+

languages/go/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
2+
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=

languages/go/setup.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
5+
GO_ARCH="$(uname -m | sed 's/x86_64/x64/' | sed 's/aarch64/arm64/')"
6+
7+
mkdir -p "$REPO_ROOT"/languages/go/internal/cinterface/lib/{darwin,linux,windows}-{arm64,x64}
8+
9+
if [ ! -f ./target/debug/libbitwarden_c.a ]; then
10+
echo "Building bitwarden_c..."
11+
cargo build --quiet -p bitwarden-c
12+
fi
13+
14+
# windows can be either mingw, msys, or cygwin
15+
if [[ "$OS" = *"mingw"* ]] || [[ "$OS" = *"msys"* ]] || [[ "$OS" = *"cygwin"* ]]; then
16+
OS="windows" # normalize to windows
17+
ln -f "$REPO_ROOT/target/debug/bitwarden_c.dll" "$REPO_ROOT/languages/go/internal/cinterface/lib/$OS-$GO_ARCH/bitwarden_c.dll" || {
18+
echo "Failed to symlink bitwarden_c.dll"
19+
exit 1
20+
}
21+
else
22+
ln -f "$REPO_ROOT/target/debug/libbitwarden_c.a" "$REPO_ROOT/languages/go/internal/cinterface/lib/$OS-$GO_ARCH/libbitwarden_c.a" || {
23+
echo "Failed to symlink libbitwarden_c.a"
24+
exit 1
25+
}
26+
fi

languages/go/test.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
pushd "$REPO_ROOT"/languages/go > /dev/null || exit 1
5+
6+
go test || exit 1
7+
8+
popd > /dev/null || exit 1

0 commit comments

Comments
 (0)