Skip to content

Commit 7d1e564

Browse files
committed
Adding tests and gh actions to build and package
1 parent 6f872d6 commit 7d1e564

File tree

8 files changed

+447
-3
lines changed

8 files changed

+447
-3
lines changed

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Test and submit dependencies
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up JDK 17
18+
uses: actions/setup-java@v4
19+
with:
20+
java-version: "17"
21+
distribution: "temurin"
22+
23+
- name: Setup Gradle
24+
uses: gradle/actions/setup-gradle@v4
25+
26+
- name: Run tests
27+
run: ./gradlew test
28+
29+
dependency-submission:
30+
runs-on: ubuntu-latest
31+
permissions:
32+
contents: write
33+
34+
steps:
35+
- uses: actions/checkout@v4
36+
- name: Set up JDK 17
37+
uses: actions/setup-java@v4
38+
with:
39+
java-version: "17"
40+
distribution: "temurin"
41+
42+
- name: Generate and submit dependency graph
43+
uses: gradle/actions/dependency-submission@v4

.github/workflows/publish.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Package and publish
2+
3+
on:
4+
release:
5+
types: [created]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
packages: write
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Set up JDK 17
16+
uses: actions/setup-java@v4
17+
with:
18+
java-version: "17"
19+
distribution: "temurin"
20+
server-id: github
21+
settings-path: ${{ github.workspace }} # location for the settings.xml file
22+
23+
- name: Setup Gradle
24+
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
25+
26+
- name: Build with Gradle
27+
run: ./gradlew s2:build
28+
29+
- name: Publish to GitHub Packages
30+
run: ./gradlew s2:publish
31+
env:
32+
GH_ACTOR: ${{ github.actor }}
33+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
33

44
[versions]
5-
junit-jupiter = "5.9.2"
5+
junit-jupiter = "5.11.4"
66

77
[libraries]
88
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

s2/build.gradle.kts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ val grpcVersion = "1.64.0"
1212
val protobufVersion = "3.25.0"
1313
val tomcatAnnotationsVersion = "11.0.2"
1414
val javaxAnnotationVersion = "1.3.2"
15+
val mockitoVersion = "5.8.0"
16+
val assertJVersion = "3.24.2"
1517

1618
dependencies {
1719
implementation("io.grpc:grpc-protobuf:$grpcVersion")
@@ -24,10 +26,17 @@ dependencies {
2426

2527
compileOnly("org.apache.tomcat:tomcat-annotations-api:$tomcatAnnotationsVersion")
2628

27-
28-
29+
testImplementation(platform("org.junit:junit-bom:5.11.4"))
2930
testImplementation(libs.junit.jupiter)
3031
testImplementation("io.grpc:grpc-testing:$grpcVersion")
32+
testImplementation("io.grpc:grpc-inprocess:$grpcVersion")
33+
testImplementation("org.mockito:mockito-core:$mockitoVersion")
34+
testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") {
35+
exclude(group = "org.junit.jupiter")
36+
}
37+
testImplementation("org.assertj:assertj-core:$assertJVersion")
38+
39+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
3140
}
3241

3342
protobuf {
@@ -54,6 +63,9 @@ tasks.withType<JavaCompile>().configureEach {
5463

5564
tasks.test {
5665
useJUnitPlatform()
66+
testLogging {
67+
events("passed", "skipped", "failed")
68+
}
5769
}
5870

5971
java {
@@ -70,5 +82,15 @@ publishing {
7082
version = "0.0.1"
7183
from(components["java"])
7284
}
85+
repositories {
86+
maven {
87+
name = "GitHubPackages"
88+
url = uri("https://maven.pkg.github.com/twelvehart/s2-java-sdk")
89+
credentials {
90+
username = System.getenv("GH_ACTOR")
91+
password = System.getenv("GH_TOKEN")
92+
}
93+
}
94+
}
7395
}
7496
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package s2.auth;
2+
3+
import io.grpc.CallCredentials;
4+
import io.grpc.Metadata;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.ExtendWith;
7+
import org.mockito.ArgumentCaptor;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
11+
import java.util.concurrent.Executor;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
15+
import static org.mockito.Mockito.verify;
16+
17+
@ExtendWith(MockitoExtension.class)
18+
class BearerTokenCallCredentialsTest {
19+
20+
@Mock
21+
private CallCredentials.MetadataApplier metadataApplier;
22+
23+
@Test
24+
void shouldAddBearerTokenToMetadata() {
25+
var token = "test-token";
26+
var credentials = new BearerTokenCallCredentials(token);
27+
Executor directExecutor = Runnable::run;
28+
29+
credentials.applyRequestMetadata(null, directExecutor, metadataApplier);
30+
31+
var metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
32+
verify(metadataApplier).apply(metadataCaptor.capture());
33+
34+
var capturedMetadata = metadataCaptor.getValue();
35+
var authHeader =
36+
capturedMetadata.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER));
37+
38+
assertThat(authHeader).isNotNull().isEqualTo("Bearer " + token);
39+
}
40+
41+
@Test
42+
void shouldRejectNullToken() {
43+
assertThatThrownBy(() -> new BearerTokenCallCredentials(null))
44+
.isInstanceOf(IllegalArgumentException.class)
45+
.hasMessageContaining("Token cannot be null or empty");
46+
}
47+
48+
@Test
49+
void shouldRejectEmptyToken() {
50+
assertThatThrownBy(() -> new BearerTokenCallCredentials(" "))
51+
.isInstanceOf(IllegalArgumentException.class)
52+
.hasMessageContaining("Token cannot be null or empty");
53+
}
54+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package s2.channel;
2+
3+
import org.junit.jupiter.api.Test;
4+
import java.util.concurrent.TimeUnit;
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
7+
class ChannelManagerTest {
8+
9+
@Test
10+
void shouldCreateAndManageChannelsCorrectly() {
11+
var host = "localhost";
12+
var port = 50051;
13+
var useTls = false;
14+
try (var channelManager = new ChannelManager(host, port, useTls)) {
15+
var channel1 = channelManager.createChannel();
16+
var currentChannel = channelManager.getCurrentChannel();
17+
18+
// Then initial channel should be set
19+
assertThat(channel1).isNotNull();
20+
assertThat(currentChannel).isPresent();
21+
assertThat(currentChannel.get()).isSameAs(channel1);
22+
23+
// When switching to a basin
24+
var basinChannel = channelManager.switchToBasin("test-basin");
25+
var updatedChannel = channelManager.getCurrentChannel();
26+
27+
// Then basin channel should be current and different from initial channel
28+
assertThat(basinChannel).isNotNull();
29+
assertThat(updatedChannel).isPresent();
30+
assertThat(updatedChannel.get()).isSameAs(basinChannel);
31+
assertThat(updatedChannel.get()).isNotSameAs(channel1);
32+
33+
// Assert both channels are not shutdown until explicitly closed
34+
assertThat(channel1.isShutdown()).isFalse();
35+
assertThat(basinChannel.isShutdown()).isFalse();
36+
}
37+
}
38+
39+
@Test
40+
void shouldHandleChannelShutdownGracefully() throws InterruptedException {
41+
// Given
42+
var host = "localhost";
43+
var port = 50052;
44+
var useTls = false;
45+
46+
var channelManager = new ChannelManager(host, port, useTls);
47+
48+
// When
49+
var channel = channelManager.createChannel();
50+
channelManager.close();
51+
52+
// Then
53+
assertThat(channel.isShutdown()).isTrue();
54+
assertThat(channel.awaitTermination(5, TimeUnit.SECONDS)).isTrue();
55+
}
56+
57+
@Test
58+
void shouldCreateBasinChannelWithCorrectHost() {
59+
var host = "example.com";
60+
var port = 50053;
61+
var useTls = false;
62+
var basinName = "test-basin";
63+
64+
try (var channelManager = new ChannelManager(host, port, useTls)) {
65+
var basinChannel = channelManager.createBasinChannel(basinName);
66+
67+
assertThat(basinChannel).isNotNull();
68+
assertThat(basinChannel.isShutdown()).isFalse();
69+
}
70+
}
71+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package s2.services;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
11+
import io.grpc.ManagedChannel;
12+
import io.grpc.CallCredentials;
13+
import s2.auth.BearerTokenCallCredentials;
14+
15+
@ExtendWith(MockitoExtension.class)
16+
public class ClientBuilderTest {
17+
@Mock
18+
private ManagedChannel mockChannel;
19+
20+
private static final String TEST_HOST = "test.s2.dev";
21+
private static final String TEST_TOKEN = "test-token";
22+
private static final String TEST_BASIN = "test-basin";
23+
24+
@Test
25+
void builderShouldCreateClientWithMinimalParameters() {
26+
Client client = Client.newBuilder().host(TEST_HOST).bearerToken(TEST_TOKEN).build();
27+
28+
assertNotNull(client);
29+
assertNotNull(client.account());
30+
assertNotNull(client.basin());
31+
assertNotNull(client.stream());
32+
assertNotNull(client.streamAsync());
33+
}
34+
35+
@Test
36+
void builderShouldCreateClientWithInjectedChannel() {
37+
CallCredentials credentials = new BearerTokenCallCredentials(TEST_TOKEN);
38+
Client client = Client.newBuilder().channel(mockChannel).credentials(credentials).build();
39+
40+
assertNotNull(client);
41+
}
42+
43+
@Test
44+
void builderShouldThrowWhenMissingRequiredParameters() {
45+
ClientBuilder builder = Client.newBuilder();
46+
47+
assertThrows(IllegalStateException.class, builder::build,
48+
"Should throw when no parameters are set");
49+
50+
assertThrows(IllegalStateException.class, () -> builder.host(TEST_HOST).build(),
51+
"Should throw when bearer token is missing");
52+
53+
ClientBuilder tokenBuilder = Client.newBuilder();
54+
assertThrows(IllegalStateException.class, () -> tokenBuilder.bearerToken(TEST_TOKEN).build(),
55+
"Should throw when host is missing");
56+
}
57+
58+
@Test
59+
void useBasinShouldThrowWithInjectedChannel() {
60+
CallCredentials credentials = new BearerTokenCallCredentials(TEST_TOKEN);
61+
Client client = Client.newBuilder().channel(mockChannel).credentials(credentials).build();
62+
63+
assertThrows(UnsupportedOperationException.class, () -> client.useBasin(TEST_BASIN),
64+
"Should throw when using injected channel");
65+
}
66+
67+
@Test
68+
void useBasinShouldWorkWithManagedChannel() {
69+
Client client = Client.newBuilder().host(TEST_HOST).bearerToken(TEST_TOKEN).build();
70+
71+
// This should not throw
72+
assertDoesNotThrow(() -> client.useBasin(TEST_BASIN));
73+
}
74+
75+
@Test
76+
void closeShouldWorkWithManagedChannel() {
77+
Client client = Client.newBuilder().host(TEST_HOST).bearerToken(TEST_TOKEN).build();
78+
79+
// This should not throw
80+
assertDoesNotThrow(client::close);
81+
}
82+
83+
@Test
84+
void closeShouldNotAffectInjectedChannel() {
85+
CallCredentials credentials = new BearerTokenCallCredentials(TEST_TOKEN);
86+
Client client = Client.newBuilder().channel(mockChannel).credentials(credentials).build();
87+
88+
client.close();
89+
90+
// Verify channel was not shut down
91+
verify(mockChannel, never()).shutdown();
92+
}
93+
}

0 commit comments

Comments
 (0)