Add javax.naming Test Fixtures

Added some test fixtures to reduce mocking that was dependent on
implementation details not under test.

With these in place, it is easier to construct test versions of
common javax.naming constructs while focusing on mocking
Spring LDAP behavior.

Issue gh-446
This commit is contained in:
Josh Cummings
2024-10-10 12:08:49 -06:00
parent c0497f5455
commit 9779735dac
4 changed files with 598 additions and 0 deletions

View File

@@ -0,0 +1,451 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InvalidAttributeValueException;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.CollectionUtils;
class MockDirContext implements DirContext {
private boolean closed;
private Map<String, Map<String, List<Object>>> entries = new HashMap<>();
private Map<String, Object> objects = new HashMap<>();
private Map<String, List<Object>> require(Name name) throws NamingException {
return require(name.toString());
}
private Map<String, List<Object>> require(String name) throws NamingException {
if (!this.entries.containsKey(name)) {
throw new NameNotFoundException("name not found");
}
return this.entries.get(name);
}
private NameAwareAttributes toAttributes(Map<String, List<Object>> map) {
NameAwareAttributes attributes = new NameAwareAttributes();
for (Map.Entry<String, List<Object>> entry : map.entrySet()) {
NameAwareAttribute attribute = new NameAwareAttribute(entry.getKey(), true);
entry.getValue().forEach(attribute::add);
attributes.put(attribute);
}
return attributes;
}
private Map<String, List<Object>> toEntry(NameAwareAttributes attributes) {
Map<String, List<Object>> map = new HashMap<>();
Iterator<NameAwareAttribute> iterator = CollectionUtils.toIterator(attributes.getAll());
while (iterator.hasNext()) {
NameAwareAttribute attribute = iterator.next();
List<Object> values = new ArrayList<>();
CollectionUtils.toIterator(attribute.getAll()).forEachRemaining(values::add);
map.put(attribute.getID(), values);
}
return map;
}
@Override
public Attributes getAttributes(Name name) throws NamingException {
return toAttributes(require(name));
}
@Override
public Attributes getAttributes(String name) throws NamingException {
return toAttributes(require(name));
}
@Override
public Attributes getAttributes(Name name, String[] attrIds) throws NamingException {
Map<String, List<Object>> entry = require(name);
NameAwareAttributes toReturn = new NameAwareAttributes();
for (String attrId : attrIds) {
entry.get(attrId);
toReturn.put(attrId, entry.get(attrId));
}
return toReturn;
}
@Override
public Attributes getAttributes(String name, String[] attrIds) throws NamingException {
return getAttributes(LdapUtils.newLdapName(name), attrIds);
}
@Override
public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException {
modifyAttributes(name.toString(), mod_op, attrs);
}
@Override
public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException {
Map<String, List<Object>> entry = require(name);
Map<String, List<Object>> toModify = toEntry((NameAwareAttributes) attrs);
if (mod_op == DirContext.ADD_ATTRIBUTE) {
for (Map.Entry<String, List<Object>> attribute : toModify.entrySet()) {
List<Object> toAdd = attribute.getValue();
if (toAdd.isEmpty()) {
throw new InvalidAttributeValueException("cannot add empty attribute");
}
entry.computeIfPresent(attribute.getKey(), (k, v) -> {
v.addAll(toAdd);
return v;
});
entry.computeIfAbsent(attribute.getKey(), (k) -> new ArrayList<>()).addAll(toAdd);
}
return;
}
if (mod_op == DirContext.REPLACE_ATTRIBUTE) {
for (Map.Entry<String, List<Object>> attribute : toModify.entrySet()) {
List<Object> toAdd = attribute.getValue();
if (toAdd.isEmpty()) {
entry.remove(attribute.getKey());
}
else {
entry.put(attribute.getKey(), toAdd);
}
}
return;
}
if (mod_op == DirContext.REMOVE_ATTRIBUTE) {
for (Map.Entry<String, List<Object>> attribute : toModify.entrySet()) {
List<Object> toRemove = attribute.getValue();
entry.computeIfPresent(attribute.getKey(), (k, v) -> {
v.removeAll(toRemove);
return v;
});
if (entry.get(attribute.getKey()).isEmpty()) {
entry.remove(attribute.getKey());
}
}
}
}
@Override
public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException {
modifyAttributes(name.toString(), mods);
}
@Override
public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException {
for (ModificationItem item : mods) {
NameAwareAttributes attributes = new NameAwareAttributes();
attributes.put(item.getAttribute());
modifyAttributes(name, item.getModificationOp(), attributes);
}
}
@Override
public void bind(Name name, Object obj, Attributes attrs) throws NamingException {
bind(name.toString(), obj, attrs);
}
@Override
public void bind(String name, Object obj, Attributes attrs) throws NamingException {
if (this.objects.containsKey(name)) {
throw new NameAlreadyBoundException("name already bound");
}
if (obj != null) {
this.objects.put(name, obj);
}
if (attrs.size() > 0) {
Map<String, List<Object>> entry = toEntry((NameAwareAttributes) attrs);
this.entries.put(name, entry);
return;
}
if (obj instanceof DirContext ctx) {
Map<String, List<Object>> entry = toEntry((NameAwareAttributes) ctx.getAttributes(name));
this.entries.put(name, entry);
}
}
@Override
public void rebind(Name name, Object obj, Attributes attrs) throws NamingException {
}
@Override
public void rebind(String name, Object obj, Attributes attrs) throws NamingException {
}
@Override
public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException {
return null;
}
@Override
public DirContext createSubcontext(String name, Attributes attrs) throws NamingException {
return null;
}
@Override
public DirContext getSchema(Name name) throws NamingException {
return null;
}
@Override
public DirContext getSchema(String name) throws NamingException {
return null;
}
@Override
public DirContext getSchemaClassDefinition(Name name) throws NamingException {
return null;
}
@Override
public DirContext getSchemaClassDefinition(String name) throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes, String[] attributesToReturn)
throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes,
String[] attributesToReturn) throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes) throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(Name name, String filter, SearchControls cons)
throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons)
throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(Name name, String filterExpr, Object[] filterArgs,
SearchControls cons) throws NamingException {
return null;
}
@Override
public NamingEnumeration<SearchResult> search(String name, String filterExpr, Object[] filterArgs,
SearchControls cons) throws NamingException {
return null;
}
@Override
public Object lookup(Name name) throws NamingException {
return lookup(name.toString());
}
@Override
public Object lookup(String name) throws NamingException {
return this.objects.get(name);
}
@Override
public void bind(Name name, Object obj) throws NamingException {
bind(name.toString(), obj);
}
@Override
public void bind(String name, Object obj) throws NamingException {
bind(name, obj, toAttributes(Map.of(name, Collections.emptyList())));
}
@Override
public void rebind(Name name, Object obj) throws NamingException {
rebind(name.toString(), obj);
}
@Override
public void rebind(String name, Object obj) throws NamingException {
if (!this.objects.containsKey(name)) {
throw new NameNotFoundException("name not found");
}
this.objects.put(name, obj);
if (obj instanceof DirContext ctx) {
this.entries.put(name, toEntry((NameAwareAttributes) ctx.getAttributes(name)));
}
}
@Override
public void unbind(Name name) throws NamingException {
unbind(name.toString());
}
@Override
public void unbind(String name) throws NamingException {
this.entries.remove(name);
this.objects.remove(name);
}
@Override
public void rename(Name oldName, Name newName) throws NamingException {
rename(oldName.toString(), newName.toString());
}
@Override
public void rename(String oldName, String newName) throws NamingException {
if (this.entries.containsKey(newName)) {
throw new NameAlreadyBoundException("name already bound");
}
if (!this.entries.containsKey(oldName)) {
throw new NameNotFoundException("name not found");
}
Map<String, List<Object>> attributes = this.entries.remove(oldName);
Object object = this.entries.remove(oldName);
this.entries.put(newName, attributes);
this.objects.put(newName, object);
}
@Override
public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
return null;
}
@Override
public NamingEnumeration<NameClassPair> list(String name) throws NamingException {
return null;
}
@Override
public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
return null;
}
@Override
public NamingEnumeration<Binding> listBindings(String name) throws NamingException {
return null;
}
@Override
public void destroySubcontext(Name name) throws NamingException {
}
@Override
public void destroySubcontext(String name) throws NamingException {
}
@Override
public Context createSubcontext(Name name) throws NamingException {
return null;
}
@Override
public Context createSubcontext(String name) throws NamingException {
return null;
}
@Override
public Object lookupLink(Name name) throws NamingException {
return null;
}
@Override
public Object lookupLink(String name) throws NamingException {
return null;
}
@Override
public NameParser getNameParser(Name name) throws NamingException {
return null;
}
@Override
public NameParser getNameParser(String name) throws NamingException {
return null;
}
@Override
public Name composeName(Name name, Name prefix) throws NamingException {
return null;
}
@Override
public String composeName(String name, String prefix) throws NamingException {
return "";
}
@Override
public Object addToEnvironment(String propName, Object propVal) throws NamingException {
return null;
}
@Override
public Object removeFromEnvironment(String propName) throws NamingException {
return null;
}
@Override
public Hashtable<?, ?> getEnvironment() throws NamingException {
return null;
}
@Override
public void close() throws NamingException {
this.closed = true;
}
boolean isClosed() {
return this.closed;
}
@Override
public String getNameInNamespace() throws NamingException {
return "";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core;
import javax.naming.Name;
final class TestDirContextAdapters {
private TestDirContextAdapters() {
}
static DirContextAdapter forUpdate(Name dn) {
return forUpdate(dn, TestNameAwareAttributes.attributes());
}
static DirContextAdapter forUpdate(Name dn, NameAwareAttributes attributes) {
DirContextAdapter adapter = new DirContextAdapter(attributes, dn);
adapter.setUpdateMode(true);
return adapter;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core;
import javax.naming.directory.Attribute;
import javax.naming.directory.ModificationItem;
import static org.assertj.core.api.Assertions.assertThat;
final class TestModificationItems {
private TestModificationItems() {
}
static ModificationItem add(String name, Object... values) {
return add(TestNameAwareAttributes.attribute(name, values));
}
static ModificationItem add(Attribute attribute) {
return new ModificationItem(MockDirContext.ADD_ATTRIBUTE, attribute);
}
static ModificationItem replace(String name, Object... values) {
return remove(name, TestNameAwareAttributes.attribute(name, values));
}
static ModificationItem replace(Attribute attribute) {
return new ModificationItem(MockDirContext.REPLACE_ATTRIBUTE, attribute);
}
static ModificationItem remove(String name, Object... values) {
return remove(TestNameAwareAttributes.attribute(name, values));
}
static ModificationItem remove(Attribute attribute) {
return new ModificationItem(MockDirContext.REMOVE_ATTRIBUTE, attribute);
}
static void assertEquals(ModificationItem actual, ModificationItem expected) {
assertThat(actual.getAttribute()).isEqualTo(expected.getAttribute());
assertThat(actual.getModificationOp()).isEqualTo(expected.getModificationOp());
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core;
final class TestNameAwareAttributes {
private TestNameAwareAttributes() {
}
static NameAwareAttributes attributes() {
NameAwareAttributes attributes = new NameAwareAttributes();
attributes.put(attribute());
return attributes;
}
static NameAwareAttributes attributes(NameAwareAttribute... list) {
NameAwareAttributes attributes = new NameAwareAttributes();
for (NameAwareAttribute attribute : list) {
attributes.put(attribute);
}
return attributes;
}
static NameAwareAttribute attribute() {
return new NameAwareAttribute("name", "value");
}
static NameAwareAttribute attribute(String name, Object... values) {
NameAwareAttribute attribute = new NameAwareAttribute(name);
for (Object value : values) {
attribute.add(value);
}
return attribute;
}
}