listAllSeats(int pageSize, int pageOffset)
+ throws IOException {
+ return searchCopilotSeats().list().withPageSize(pageSize).withPageOffset(pageOffset);
+ }
+
public void deleteSCIMGroup(String scimGroupId) throws IOException {
root.createRequest()
.method("DELETE")
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeat.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeat.java
new file mode 100644
index 0000000..b1bdbc2
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeat.java
@@ -0,0 +1,34 @@
+package org.kohsuke.github;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class GitHubCopilotSeat {
+ @JsonProperty("created_at")
+ public String created_at;
+
+ @JsonProperty("assignee")
+ public GitHubCopilotSeatAssignee assignee;
+
+ @JsonProperty("pending_cancellation_date")
+ public String pending_cancellation_date;
+
+ @JsonProperty("plan_type")
+ public String plan_type;
+
+ @JsonProperty("last_authenticated_at")
+ public String last_authenticated_at;
+
+ @JsonProperty("updated_at")
+ public String updated_at;
+
+ @JsonProperty("last_activity_at")
+ public String last_activity_at;
+
+ @JsonProperty("last_activity_editor")
+ public String last_activity_editor;
+
+ @JsonProperty("assigning_team")
+ public GitHubCopilotSeatAssigningTeam assigning_team;
+}
\ No newline at end of file
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssignee.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssignee.java
new file mode 100644
index 0000000..0eb8a74
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssignee.java
@@ -0,0 +1,26 @@
+package org.kohsuke.github;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class GitHubCopilotSeatAssignee {
+ @JsonProperty("login")
+ public String login;
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("node_id")
+ public String node_id;
+
+ @JsonProperty("url")
+ public String url;
+
+ @JsonProperty("type")
+ public String type;
+
+ @JsonProperty("user_view_type")
+ public String user_view_type;
+
+ @JsonProperty("site_admin")
+ public String site_admin;
+}
\ No newline at end of file
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssigningTeam.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssigningTeam.java
new file mode 100644
index 0000000..41c2941
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatAssigningTeam.java
@@ -0,0 +1,24 @@
+package org.kohsuke.github;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class GitHubCopilotSeatAssigningTeam {
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("slug")
+ public String slug;
+
+ @JsonProperty("group_name")
+ public String group_name;
+
+ @JsonProperty("created_at")
+ public String created_at;
+
+ @JsonProperty("updated_at")
+ public String updated_at;
+}
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatPageIterator.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatPageIterator.java
new file mode 100644
index 0000000..ea7993a
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatPageIterator.java
@@ -0,0 +1,114 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Used for seats pagination information.
+ *
+ * This class is not thread-safe. Any one instance should only be called from a single thread.
+ *
+ * @param the type parameter
+ * @author Hiroyuki Wada
+ * @author Nikolas Correa
+ */
+public class GitHubCopilotSeatPageIterator implements Iterator {
+
+ private final GitHubClient client;
+ private final Class type;
+
+ private T next;
+
+ private GitHubRequest nextRequest;
+
+ private GitHubResponse finalResponse = null;
+
+ private GitHubCopilotSeatPageIterator(GitHubClient client, Class type, GitHubRequest request) {
+ if (!"GET".equals(request.method())) {
+ throw new IllegalStateException("Request method \"GET\" is required for page iterator.");
+ }
+
+ this.client = client;
+ this.type = type;
+ this.nextRequest = request;
+ }
+
+ static GitHubCopilotSeatPageIterator create(GitHubClient client, Class type, GitHubRequest request, int pageSize, int pageOffset) {
+
+ try {
+ if (pageSize > 0) {
+ GitHubRequest.Builder> builder = request.toBuilder().with("count", pageSize);
+ if (pageOffset > 0) {
+ builder.with("startIndex", pageOffset);
+ }
+ request = builder.build();
+ }
+
+ return new GitHubCopilotSeatPageIterator<>(client, type, request);
+ } catch (MalformedURLException e) {
+ throw new GHException("Unable to build GitHub SCIM API URL", e);
+ }
+ }
+
+ public boolean hasNext() {
+ fetch();
+ return next != null;
+ }
+
+ public T next() {
+ fetch();
+ T result = next;
+ if (result == null)
+ throw new NoSuchElementException();
+ next = null;
+ return result;
+ }
+
+ public GitHubResponse finalResponse() {
+ if (hasNext()) {
+ throw new GHException("Final response is not available until after iterator is done.");
+ }
+ return finalResponse;
+ }
+
+ private void fetch() {
+ if (next != null || nextRequest == null)
+ return;
+
+ URL url = nextRequest.url();
+ try {
+ GitHubResponse nextResponse = client.sendRequest(nextRequest,
+ (responseInfo) -> GitHubResponse.parseBody(responseInfo, type));
+ next = nextResponse.body();
+ nextRequest = findNextURL(nextResponse);
+ if (nextRequest == null) {
+ finalResponse = nextResponse;
+ }
+ } catch (IOException e) {
+ throw new GHException("Failed to retrieve " + url, e);
+ }
+ }
+
+ private GitHubRequest findNextURL(GitHubResponse response) {
+ String linkHeader = response.headerField("Link");
+ if (linkHeader == null) return null;
+
+ // Expressão para capturar: ; rel="next"
+ Pattern pattern = Pattern.compile("<([^>]+)>;\\s*rel=\"next\"");
+ Matcher matcher = pattern.matcher(linkHeader);
+ if (matcher.find()) {
+ String nextUrl = matcher.group(1);
+ try {
+ return GitHubRequest.newBuilder().withUrlPath(nextUrl).build();
+ } catch (Exception e) {
+ throw new GHException("Malformed next URL: " + nextUrl, e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatPagedSearchIterable.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatPagedSearchIterable.java
new file mode 100644
index 0000000..f442501
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatPagedSearchIterable.java
@@ -0,0 +1,69 @@
+package org.kohsuke.github;
+
+import java.util.Iterator;
+
+/**
+ * {@link PagedIterable} enhanced to report search result specific information.
+ *
+ * @param the type parameter
+ * @author Hiroyuki Wada
+ * @author Nikolas Correa
+ */
+public class GitHubCopilotSeatPagedSearchIterable extends PagedIterable {
+ private final transient GitHub root;
+
+ private final GitHubRequest request;
+
+ private final Class extends GitHubCopilotSeatsSearchResult> receiverType;
+
+ private GitHubCopilotSeatsSearchResult result;
+ private int pageOffset;
+
+ public GitHubCopilotSeatPagedSearchIterable(GitHub root, GitHubRequest request, Class extends GitHubCopilotSeatsSearchResult> receiverType) {
+ this.root = root;
+ this.request = request;
+ this.receiverType = receiverType;
+ }
+
+ @Override
+ public GitHubCopilotSeatPagedSearchIterable withPageSize(int size) {
+ return (GitHubCopilotSeatPagedSearchIterable) super.withPageSize(size);
+ }
+
+ public GitHubCopilotSeatPagedSearchIterable withPageOffset(int pageOffset) {
+ this.pageOffset = pageOffset;
+ return this;
+ }
+
+ public int getTotalSeats() {
+ populate();
+ return result.total_seats;
+ }
+
+ private void populate() {
+ if (result == null)
+ iterator().hasNext(); // dispara a carga inicial
+ }
+
+ @Override
+ public PagedIterator _iterator(int pageSize) {
+ final Iterator adapter = adapt(
+ GitHubCopilotSeatPageIterator.create(root.getClient(), receiverType, request, pageSize, pageOffset));
+ return new PagedIterator<>(adapter, null);
+ }
+
+ protected Iterator adapt(final Iterator extends GitHubCopilotSeatsSearchResult> base) {
+ return new Iterator() {
+ public boolean hasNext() {
+ return base.hasNext();
+ }
+
+ public T[] next() {
+ GitHubCopilotSeatsSearchResult v = base.next();
+ if (result == null)
+ result = v;
+ return v.seats;
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchBuilder.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchBuilder.java
new file mode 100644
index 0000000..d9cd9be
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchBuilder.java
@@ -0,0 +1,52 @@
+package org.kohsuke.github;
+
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Search for GitHub Copilot seats - Enterprise plan.
+ *
+ * @author Hiroyuki Wada
+ * @author Nikolas Correa
+ */
+
+public class GitHubCopilotSeatsSearchBuilder extends GHQueryBuilder {
+ protected final Map filter = new HashMap<>();
+
+ private final Class extends GitHubCopilotSeatsSearchResult> receiverType;
+
+ protected final GHEnterpriseExt enterprise;
+
+ GitHubCopilotSeatsSearchBuilder(GitHub root, GHEnterpriseExt enterprise) {
+ super(root);
+ this.enterprise = enterprise;
+ this.receiverType = CopilotSeatSearchResult.class;
+
+ req.withUrlPath(getApiUrl());
+ req.withHeader(SCIMConstants.HEADER_ACCEPT, "application/json");
+ req.withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION);
+ req.rateLimit(RateLimitTarget.SEARCH);
+ }
+
+ public GitHubCopilotSeatsSearchBuilder eq(String key, String value) {
+ filter.put(key, value);
+ return this;
+ }
+
+ @Override
+ public GitHubCopilotSeatPagedSearchIterable list() {
+ try {
+ return new GitHubCopilotSeatPagedSearchIterable<>(root, req.build(), receiverType);
+ } catch (MalformedURLException e) {
+ throw new GHException("", e);
+ }
+ }
+
+ protected String getApiUrl() {
+ return String.format("/enterprises/%s/copilot/billing/seats", enterprise.login);
+ }
+
+ private static class CopilotSeatSearchResult extends GitHubCopilotSeatsSearchResult {
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchResult.java b/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchResult.java
new file mode 100644
index 0000000..f421036
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GitHubCopilotSeatsSearchResult.java
@@ -0,0 +1,11 @@
+package org.kohsuke.github;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class GitHubCopilotSeatsSearchResult {
+ @JsonProperty("total_seats")
+ public int total_seats;
+
+ @JsonProperty("seats")
+ public T[] seats;
+}
\ No newline at end of file
diff --git a/src/test/java/jp/openstandia/connector/github/CreateUserOpTest.java b/src/test/java/jp/openstandia/connector/github/CreateUserOpTest.java
new file mode 100644
index 0000000..80470f6
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/CreateUserOpTest.java
@@ -0,0 +1,34 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.*;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static jp.openstandia.connector.github.GitHubEMUUserHandler.USER_OBJECT_CLASS;
+
+public class CreateUserOpTest extends AbstractEMUTest {
+
+ private Set userEntry() {
+
+ Set attributeSet = new HashSet<>();
+ attributeSet.add(AttributeBuilder.build(Name.NAME, ""));
+ attributeSet.add(AttributeBuilder.build("externalId", ""));
+ attributeSet.add(AttributeBuilder.build("displayName", ""));
+ attributeSet.add(AttributeBuilder.build("primaryEmail", ""));
+ attributeSet.add(AttributeBuilder.build("primaryRole", "User"));
+ attributeSet.add(AttributeBuilder.build(OperationalAttributes.ENABLE_NAME, true));
+ return attributeSet;
+ }
+
+ @Test()
+ public void shouldCreateOrReturnExistentUser() {
+ ConnectorFacade facade = newFacade();
+ Uid uid = facade.create(USER_OBJECT_CLASS, userEntry(), null);
+ AssertJUnit.assertNotNull(uid);
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/DeleteUsersOpTest.java b/src/test/java/jp/openstandia/connector/github/DeleteUsersOpTest.java
new file mode 100644
index 0000000..8a7a61e
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/DeleteUsersOpTest.java
@@ -0,0 +1,19 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.testng.annotations.Test;
+
+import static jp.openstandia.connector.github.GitHubEMUUserHandler.USER_OBJECT_CLASS;
+
+public class DeleteUsersOpTest extends AbstractEMUTest {
+
+ String userUidToDelete = "";
+
+ @Test()
+ public void shouldDeleteUserIfExists() {
+ ConnectorFacade facade = newFacade();
+ facade.delete(USER_OBJECT_CLASS, new Uid(userUidToDelete), null);
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/EMUSchemaTest.java b/src/test/java/jp/openstandia/connector/github/EMUSchemaTest.java
index 92dd9bf..793d4ac 100644
--- a/src/test/java/jp/openstandia/connector/github/EMUSchemaTest.java
+++ b/src/test/java/jp/openstandia/connector/github/EMUSchemaTest.java
@@ -31,12 +31,14 @@ void schema() {
Schema schema = connector.schema();
assertNotNull(schema);
- assertEquals(2, schema.getObjectClassInfo().size());
+ assertEquals(3, schema.getObjectClassInfo().size());
Optional user = schema.getObjectClassInfo().stream().filter(o -> o.is("EMUUser")).findFirst();
Optional team = schema.getObjectClassInfo().stream().filter(o -> o.is("EMUGroup")).findFirst();
+ Optional seat = schema.getObjectClassInfo().stream().filter(o -> o.is("GitHubCopilotSeat")).findFirst();
assertTrue(user.isPresent());
assertTrue(team.isPresent());
+ assertTrue(seat.isPresent());
}
}
\ No newline at end of file
diff --git a/src/test/java/jp/openstandia/connector/github/ListResultHandler.java b/src/test/java/jp/openstandia/connector/github/ListResultHandler.java
new file mode 100644
index 0000000..e1653f8
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/ListResultHandler.java
@@ -0,0 +1,19 @@
+package jp.openstandia.connector.github;
+
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ResultsHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListResultHandler implements ResultsHandler {
+
+ private final List objects = new ArrayList<>();
+ @Override
+ public boolean handle(ConnectorObject connectorObject) {
+ objects.add(connectorObject);
+ return true;
+ }
+
+ public List getObjects() { return objects; }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/SearchGroupsOpTest.java b/src/test/java/jp/openstandia/connector/github/SearchGroupsOpTest.java
new file mode 100644
index 0000000..6ba39c1
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/SearchGroupsOpTest.java
@@ -0,0 +1,54 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import static jp.openstandia.connector.github.GitHubEMUGroupHandler.GROUP_OBJECT_CLASS;
+
+public class SearchGroupsOpTest extends AbstractEMUTest {
+
+ String groupUid = "";
+ String groupName = "";
+
+ @Test()
+ public void shouldReturnAllGroups() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ facade.search(GROUP_OBJECT_CLASS, null, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertTrue("Size: " + objects.size(), objects.size() > 1);
+ }
+
+ @Test()
+ public void shouldReturnGroupByUid() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Uid.NAME, groupUid);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(GROUP_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+
+ @Test()
+ public void shouldReturnGroupByName() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Name.NAME, groupName);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(GROUP_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/SearchSeatsOpTest.java b/src/test/java/jp/openstandia/connector/github/SearchSeatsOpTest.java
new file mode 100644
index 0000000..71c6083
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/SearchSeatsOpTest.java
@@ -0,0 +1,54 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import static jp.openstandia.connector.github.GitHubCopilotSeatHandler.SEAT_OBJECT_CLASS;
+
+public class SearchSeatsOpTest extends AbstractEMUTest {
+
+ String seatUid = "";
+ String seatName = "";
+
+ @Test()
+ public void shouldReturnAllSeats() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ facade.search(SEAT_OBJECT_CLASS, null, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertTrue("Size: " + objects.size(), objects.size() > 1);
+ }
+
+ @Test()
+ public void shouldReturnSeatByUid() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Uid.NAME, seatUid);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(SEAT_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+
+ @Test()
+ public void shouldReturnSeatByName() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Name.NAME, seatName);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(SEAT_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/SearchUsersOpTest.java b/src/test/java/jp/openstandia/connector/github/SearchUsersOpTest.java
new file mode 100644
index 0000000..d8afc4c
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/SearchUsersOpTest.java
@@ -0,0 +1,54 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import static jp.openstandia.connector.github.GitHubEMUUserHandler.USER_OBJECT_CLASS;
+
+public class SearchUsersOpTest extends AbstractEMUTest {
+
+ String userUid = "";
+ String userName = "";
+
+ @Test()
+ public void shouldReturnAllUsers() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ facade.search(USER_OBJECT_CLASS, null, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertTrue("Size: " + objects.size(), objects.size() > 1);
+ }
+
+ @Test()
+ public void shouldReturnUserByUid() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Uid.NAME, userUid);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(USER_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+
+ @Test()
+ public void shouldReturnUserByUsername() {
+ ConnectorFacade facade = newFacade();
+ ListResultHandler handler = new ListResultHandler();
+
+ Attribute attribute = AttributeBuilder.build(Name.NAME, userName);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(USER_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/TestOpTest.java b/src/test/java/jp/openstandia/connector/github/TestOpTest.java
new file mode 100644
index 0000000..4b7d7f9
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/TestOpTest.java
@@ -0,0 +1,14 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.testng.annotations.Test;
+
+public class TestOpTest extends AbstractEMUTest {
+
+ @Test()
+ public void shouldInitializeConnection() {
+ ConnectorFacade facade = newFacade();
+ facade.test();
+ }
+}
diff --git a/src/test/java/jp/openstandia/connector/github/UpdateGroupsOpTest.java b/src/test/java/jp/openstandia/connector/github/UpdateGroupsOpTest.java
new file mode 100644
index 0000000..6257c06
--- /dev/null
+++ b/src/test/java/jp/openstandia/connector/github/UpdateGroupsOpTest.java
@@ -0,0 +1,80 @@
+package jp.openstandia.connector.github;
+
+import jp.openstandia.connector.github.testutil.AbstractEMUTest;
+import org.identityconnectors.framework.api.ConnectorFacade;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static jp.openstandia.connector.github.GitHubEMUGroupHandler.GROUP_OBJECT_CLASS;
+
+public class UpdateGroupsOpTest extends AbstractEMUTest {
+
+ String userUid = "";
+ String groupUidToUpdate = "";
+
+ @Test()
+ public void shouldAddUserToGroup() {
+ // Create an AttributeDelta to add user uid
+ Set attributes = new HashSet<>();
+ AttributeDeltaBuilder deltaBuilder = new AttributeDeltaBuilder();
+ deltaBuilder.setName("members.User.value");
+ deltaBuilder.addValueToAdd(userUid);
+ attributes.add(deltaBuilder.build());
+
+ // Call updateDelta to update the group
+ ConnectorFacade facade = newFacade();
+ facade.updateDelta(GROUP_OBJECT_CLASS, new Uid(groupUidToUpdate), attributes, null);
+
+ // Retrieve and verify the updated object
+ ListResultHandler handler = new ListResultHandler();
+ Attribute attribute = AttributeBuilder.build(Uid.NAME, groupUidToUpdate);
+ EqualsFilter filter = new EqualsFilter(attribute);
+
+ facade.search(GROUP_OBJECT_CLASS, filter, handler, null);
+ List objects = handler.getObjects();
+ AssertJUnit.assertEquals(1, objects.size());
+
+ ConnectorObject object = objects.get(0);
+ Attribute memberOfAttr = object.getAttributeByName("members.User.value");
+ AssertJUnit.assertNotNull(memberOfAttr);
+
+ List