Skip to content
Open
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
69 changes: 69 additions & 0 deletions src/test/java/org/discordbots/api/DBLAPITest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.discordbots.api;

import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import okhttp3.OkHttpClient;
import org.discordbots.api.entity.PaginatedVotes;
import org.discordbots.api.entity.UserSource;
import org.discordbots.api.interceptors.GetSelfInterceptor;
import org.discordbots.api.interceptors.GetVoteInterceptor;
import org.discordbots.api.interceptors.GetVotesInterceptor;
import org.discordbots.api.interceptors.PostCommandsInterceptor;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

public class DBLAPITest {
private static DBLAPI CLIENT;

@BeforeAll
public static void setup() {
CLIENT =
new DBLAPI(
new OkHttpClient.Builder()
.addInterceptor(new GetSelfInterceptor())
.addInterceptor(new GetVoteInterceptor())
.addInterceptor(new GetVotesInterceptor())
.addInterceptor(new PostCommandsInterceptor())
.build());
}

@Test
public void getSelf() {
CLIENT.getSelf().toCompletableFuture().join();
}

@Test
public void postCommands() {
final JsonArray commands =
JsonParser.parseReader(
new InputStreamReader(
getClass().getClassLoader().getResourceAsStream("PostCommands.json"),
StandardCharsets.UTF_8))
.getAsJsonArray();

CLIENT.postCommands(commands).toCompletableFuture().join();
}

@ParameterizedTest
@EnumSource(UserSource.class)
public void getVote(final UserSource userSource) {
CLIENT.getVote(userSource, "123456").toCompletableFuture().join();
}

@Test
@SuppressWarnings("unused")
public void getVotes() {
final PaginatedVotes firstPage =
CLIENT
.getVotes(OffsetDateTime.of(2026, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC))
.toCompletableFuture()
.join();
final PaginatedVotes secondPage = firstPage.next().toCompletableFuture().join();
}
}
31 changes: 31 additions & 0 deletions src/test/java/org/discordbots/api/DBLWidgetTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.discordbots.api;

import org.discordbots.api.entity.ProjectType;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

public class DBLWidgetTest {
@ParameterizedTest
@EnumSource(ProjectType.class)
public void large(final ProjectType projectType) {
DBLWidget.large(projectType, "123456");
}

@ParameterizedTest
@EnumSource(ProjectType.class)
public void votes(final ProjectType projectType) {
DBLWidget.votes(projectType, "123456");
}

@ParameterizedTest
@EnumSource(ProjectType.class)
public void owner(final ProjectType projectType) {
DBLWidget.owner(projectType, "123456");
}

@ParameterizedTest
@EnumSource(ProjectType.class)
public void social(final ProjectType projectType) {
DBLWidget.social(projectType, "123456");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.discordbots.api.interceptors;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public abstract class BaseInterceptor implements Interceptor {
@SuppressWarnings("FieldMayBeFinal")
private String response;

public BaseInterceptor() {
try {
final String className = getClass().getSimpleName();

final InputStream inputStream =
BaseInterceptor.class.getResourceAsStream(
"/" + className.substring(0, className.length() - 11) + "Response.json");

response = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
} catch (final IOException | NullPointerException ignored) {
response = "";
}
}

protected abstract boolean isCorrect(final String method, final String path, final HttpUrl url);

protected abstract int getStatusCode();

protected abstract String getMessage();

@Override
public Response intercept(final Chain chain) throws IOException {
final Request request = chain.request();

final HttpUrl url = request.url();
final String path = String.join("/", url.pathSegments());

if (url.host().equals("top.gg")
&& path.startsWith("api/v1")
&& isCorrect(request.method(), path, url)) {
return new Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(getStatusCode())
.message(getMessage())
.body(ResponseBody.create(response, MediaType.get("application/json")))
.addHeader("content-type", "application/json")
.build();
}

return chain.proceed(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.discordbots.api.interceptors;

import okhttp3.HttpUrl;

public class GetSelfInterceptor extends BaseInterceptor {
@Override
protected boolean isCorrect(final String method, final String path, final HttpUrl url) {
return method.equals("GET") && path.endsWith("/projects/@me");
}

@Override
protected int getStatusCode() {
return 200;
}

@Override
protected String getMessage() {
return "OK";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.discordbots.api.interceptors;

import okhttp3.HttpUrl;

public class GetVoteInterceptor extends BaseInterceptor {
@Override
protected boolean isCorrect(final String method, final String path, final HttpUrl url) {
return method.equals("GET")
&& path.contains("/projects/@me/votes/")
&& url.queryParameter("source") != null;
}

@Override
protected int getStatusCode() {
return 200;
}

@Override
protected String getMessage() {
return "OK";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.discordbots.api.interceptors;

import okhttp3.HttpUrl;

public class GetVotesInterceptor extends BaseInterceptor {
@Override
protected boolean isCorrect(final String method, final String path, final HttpUrl url) {
return method.equals("GET")
&& path.endsWith("/projects/@me/votes")
&& (url.queryParameter("startDate") != null || url.queryParameter("cursor") != null);
}

@Override
protected int getStatusCode() {
return 200;
}

@Override
protected String getMessage() {
return "OK";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.discordbots.api.interceptors;

import okhttp3.HttpUrl;

public class PostCommandsInterceptor extends BaseInterceptor {
@Override
protected boolean isCorrect(final String method, final String path, final HttpUrl url) {
return method.equals("POST") && path.endsWith("/projects/@me/commands");
}

@Override
protected int getStatusCode() {
return 204;
}

@Override
protected String getMessage() {
return "No Content";
}
}
15 changes: 15 additions & 0 deletions src/test/java/org/discordbots/webhooks/DBLWebhooksTestSuite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.discordbots.webhooks;

import org.discordbots.webhooks.dropwizard.DBLDropwizardWebhooksTest;
import org.discordbots.webhooks.eclipsejetty.DBLEclipseJettyWebhooksTest;
import org.discordbots.webhooks.springboot.DBLSpringBootWebhooksTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({
DBLDropwizardWebhooksTest.class,
DBLEclipseJettyWebhooksTest.class,
DBLSpringBootWebhooksTest.class
})
public class DBLWebhooksTestSuite {}
47 changes: 47 additions & 0 deletions src/test/java/org/discordbots/webhooks/Mocks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.discordbots.webhooks;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class Mocks {
public final String integrationCreatePayload;
public final String integrationDeletePayload;
public final String testPayload;
public final String voteCreatePayload;

public Mocks() throws IOException, NullPointerException {
integrationCreatePayload = read("IntegrationCreate");
integrationDeletePayload = read("IntegrationDelete");
testPayload = read("Test");
voteCreatePayload = read("VoteCreate");
}

private static String read(final String name) throws IOException, NullPointerException {
final InputStream inputStream = Mocks.class.getResourceAsStream("/" + name + "Payload.json");

return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}

public static String signature(final String secret, final String body)
throws NoSuchAlgorithmException, InvalidKeyException {
final long timestamp = Instant.now().getEpochSecond();

final SecretKeySpec key =
new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
final Mac hmac = Mac.getInstance("HmacSHA256");

hmac.init(key);

final byte[] digest =
hmac.doFinal(String.format("%s.%s", timestamp, body).getBytes(StandardCharsets.UTF_8));

return "t=" + Long.toString(timestamp) + ",v1=" + HexFormat.of().formatHex(digest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.discordbots.webhooks.dropwizard;

import io.dropwizard.core.Application;
import io.dropwizard.core.Configuration;
import io.dropwizard.core.setup.Environment;

public class CustomServer extends Application<Configuration> {
public static void main(final String[] args) throws Exception {
new CustomServer().run(args);
}

@Override
public void run(final Configuration config, final Environment env) {
env.jersey().register(new CustomWebhooks());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.discordbots.webhooks.dropwizard;

import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
import org.discordbots.webhooks.payload.IntegrationCreatePayload;
import org.discordbots.webhooks.payload.IntegrationDeletePayload;
import org.discordbots.webhooks.payload.TestPayload;
import org.discordbots.webhooks.payload.VoteCreatePayload;

@Path("/webhook")
public class CustomWebhooks extends DBLWebhooks {
public CustomWebhooks() {
super(System.getenv("TOPGG_WEBHOOK_SECRET"));
}

@Override
public Response onIntegrationCreate(final IntegrationCreatePayload payload, final String trace) {
return Response.status(Response.Status.OK).entity("dw:integrationCreate," + trace).build();
}

@Override
public Response onIntegrationDelete(final IntegrationDeletePayload payload, final String trace) {
return Response.status(Response.Status.OK).entity("dw:integrationDelete," + trace).build();
}

@Override
public Response onTest(final TestPayload payload, final String trace) {
return Response.status(Response.Status.OK).entity("dw:test," + trace).build();
}

@Override
public Response onVoteCreate(final VoteCreatePayload payload, final String trace) {
return Response.status(Response.Status.OK).entity("dw:voteCreate," + trace).build();
}
}
Loading