diff --git a/src/main/java/org/couchbase/mock/client/EndureRequest.java b/src/main/java/org/couchbase/mock/client/EndureRequest.java new file mode 100644 index 0000000..2254420 --- /dev/null +++ b/src/main/java/org/couchbase/mock/client/EndureRequest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Couchbase, Inc. + * + * 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 + * + * http://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.couchbase.mock.client; + +public class EndureRequest extends MockRequest { + /** + * EndureRequest is used to fake the persistence of a key-value pair + * on a number of nodes. + * + * @param key The key to endure + * @param value An optional value + * @param onMaster Should it be persisted on the master? + * @param numReplicas THe number of replicas to persist it + */ + public EndureRequest(String key, String value, boolean onMaster, int numReplicas) { + super(); + command.put("command", "endure"); + + payload.put("Key", key); + if (value != null && !value.isEmpty()) { + payload.put("Value", value); + } + payload.put("OnMaster", onMaster); + payload.put("OnReplicas", numReplicas); + command.put("payload", payload); + } +} diff --git a/src/main/java/org/couchbase/mock/client/MockHttpClient.java b/src/main/java/org/couchbase/mock/client/MockHttpClient.java index 38d7bab..6cb3547 100644 --- a/src/main/java/org/couchbase/mock/client/MockHttpClient.java +++ b/src/main/java/org/couchbase/mock/client/MockHttpClient.java @@ -49,7 +49,7 @@ private URL buildRequestUri(MockRequest request) throws IOException { .append(":") .append(this.restAddress.getPort()) .append("/mock/") - .append(request.getName()) + .append(URLEncoder.encode(request.getName(), "UTF-8")) .append("?"); appendPayload(sb, request.getPayload()); return new URL(sb.toString()); diff --git a/src/main/java/org/couchbase/mock/control/MockCommandDispatcher.java b/src/main/java/org/couchbase/mock/control/MockCommandDispatcher.java index a2ad09c..e3ae374 100644 --- a/src/main/java/org/couchbase/mock/control/MockCommandDispatcher.java +++ b/src/main/java/org/couchbase/mock/control/MockCommandDispatcher.java @@ -65,21 +65,23 @@ private static void registerClass(MockCommand.Command cmd, Class cls) { // Instance members private final CouchbaseMock mock; - public MockCommand getCommand(String commandString, JsonObject payload) { + public MockCommand getCommand(String command, JsonObject payload) { MockCommand obj; - if (!commandMap.containsKey(commandString.toUpperCase())) { - throw new CommandNotFoundException("Unknown command: " + commandString); + command = command.replaceAll(" ", "_").toUpperCase(); + + if (!commandMap.containsKey(command)) { + throw new CommandNotFoundException("Unknown command: " + command); } MockCommand.Command cmd; Class cls; try { - cmd = MockCommand.Command.valueOf(commandString.toUpperCase()); + cmd = MockCommand.Command.valueOf(command.toUpperCase()); } catch (IllegalArgumentException e) { - throw new CommandNotFoundException("No such command: " + commandString, e); + throw new CommandNotFoundException("No such command: " + command, e); } cls = classMap.get(cmd); @@ -128,7 +130,7 @@ public String processInput(String input) { return "{ \"status\" : \"fail\", \"error\" : \"Failed to parse input\" }"; } - String command = object.get("command").getAsString().replaceAll(" ", "_"); + String command = object.get("command").getAsString(); JsonObject payload; if (!object.has("payload")) { payload = new JsonObject(); diff --git a/src/main/java/org/couchbase/mock/http/ControlHandler.java b/src/main/java/org/couchbase/mock/http/ControlHandler.java index 2d5782f..eeea247 100644 --- a/src/main/java/org/couchbase/mock/http/ControlHandler.java +++ b/src/main/java/org/couchbase/mock/http/ControlHandler.java @@ -120,10 +120,8 @@ private void _handle(HttpExchange exchange) throws IOException { } JsonObject payload = parseQueryParams(exchange); - String cmdStr = components[1]; - - MockCommand cmd = dispatcher.getCommand( - cmdStr, payload); + String cmdStr = URLDecoder.decode(components[1], "UTF-8"); + MockCommand cmd = dispatcher.getCommand(cmdStr, payload); byte[] response = cmd.getResponse().getBytes(); diff --git a/src/test/java/org/couchbase/mock/client/ClientBaseTest.java b/src/test/java/org/couchbase/mock/client/ClientBaseTest.java index 1aeb06d..beb6324 100644 --- a/src/test/java/org/couchbase/mock/client/ClientBaseTest.java +++ b/src/test/java/org/couchbase/mock/client/ClientBaseTest.java @@ -41,72 +41,20 @@ */ public abstract class ClientBaseTest extends TestCase { protected final BucketConfiguration bucketConfiguration = new BucketConfiguration(); - protected BufferedReader harakiriInput; - protected OutputStream harakiriOutput; - - public ClientBaseTest() {} + protected MockClient mockClient; + protected CouchbaseMock couchbaseMock; protected final CouchbaseConnectionFactoryBuilder cfb = new CouchbaseConnectionFactoryBuilder(); protected CouchbaseClient client; protected CouchbaseConnectionFactory connectionFactory; - protected CouchbaseMock mock; + + public ClientBaseTest() {} protected MemcachedNode getMasterForKey(String key) { VBucketNodeLocator locator = (VBucketNodeLocator) client.getNodeLocator(); return locator.getPrimary(key); } - - private interface Listener extends Runnable { - public Socket getClientSocket(); - } - - private void setupHarakiri() throws Exception { - final ServerSocket sock = new ServerSocket(0); - final int port = sock.getLocalPort(); - - Listener listener = new Listener() { - public Socket clientSocket = null; - - @Override public Socket getClientSocket() { - return clientSocket; - } - - @Override public void run() { - try { - clientSocket = sock.accept(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }; - - Thread listenThread = new Thread(listener); - String hostString = sock.getInetAddress().getHostAddress() + ":" + port; - - mock.setupHarakiriMonitor(hostString, false); - listenThread.start(); - listenThread.join(); - Socket harakiriConnection = listener.getClientSocket(); - if (harakiriConnection == null) { - throw new IOException("Couldn't get client socket"); - } - - harakiriInput = new BufferedReader( - new InputStreamReader(harakiriConnection.getInputStream())); - harakiriOutput = harakiriConnection.getOutputStream(); - - /** - * We need to negotiate the port now. - */ - StringBuilder sb = new StringBuilder(); - char c; - while ( (c = (char)harakiriInput.read()) != '\0' ) { - sb.append(c); - } - assertEquals(Integer.parseInt(sb.toString()), mock.getHttpPort()); - } - // Don't make the client flood the screen with log messages.. static { System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.SunLogger"); @@ -124,13 +72,16 @@ protected void setUp() throws Exception { bucketConfiguration.type = BucketType.COUCHBASE; ArrayList configList = new ArrayList(); configList.add(bucketConfiguration); - mock = new CouchbaseMock(0, configList); - mock.start(); - mock.waitForStartup(); - setupHarakiri(); + couchbaseMock = new CouchbaseMock(0, configList); + couchbaseMock.start(); + couchbaseMock.waitForStartup(); + + mockClient = new MockClient(new InetSocketAddress("localhost", 0)); + couchbaseMock.setupHarakiriMonitor("localhost:" + mockClient.getPort(), false); + mockClient.negotiate(); List uriList = new ArrayList(); - uriList.add(new URI("http", null, "localhost", mock.getHttpPort(), "/pools", "", "")); + uriList.add(new URI("http", null, "localhost", couchbaseMock.getHttpPort(), "/pools", "", "")); connectionFactory = cfb.buildCouchbaseConnection(uriList, bucketConfiguration.name, bucketConfiguration.password); client = new CouchbaseClient(connectionFactory); } @@ -138,9 +89,8 @@ protected void setUp() throws Exception { @Override protected void tearDown() throws Exception { client.shutdown(); - mock.stop(); - try { harakiriInput.close();} catch (IOException e) {} - try { harakiriOutput.close(); } catch (IOException e) {} + couchbaseMock.stop(); + mockClient.shutdown(); super.tearDown(); } } diff --git a/src/test/java/org/couchbase/mock/client/ClientTest.java b/src/test/java/org/couchbase/mock/client/ClientTest.java index 8c9e955..db99d53 100644 --- a/src/test/java/org/couchbase/mock/client/ClientTest.java +++ b/src/test/java/org/couchbase/mock/client/ClientTest.java @@ -111,9 +111,7 @@ public void testTTL() throws Exception { String key = "ttl_key"; OperationFuture ft = client.set(key, 1, key); ft.get(); - harakiriOutput.write("{ \"command\" : \"time travel\", \"payload\" : { \"Offset\" : 2 }}\n".getBytes()); - String response = harakiriInput.readLine(); - assertEquals("{\"status\":\"ok\"}", response); + assertTrue(mockClient.request(new TimeTravelRequest(2)).isOk()); assertNull(client.get(key)); ft = client.set(key, 10, key); ft.get(); diff --git a/src/test/java/org/couchbase/mock/client/MockAPITest.java b/src/test/java/org/couchbase/mock/client/MockAPITest.java index 2ea06bd..9965c6b 100644 --- a/src/test/java/org/couchbase/mock/client/MockAPITest.java +++ b/src/test/java/org/couchbase/mock/client/MockAPITest.java @@ -15,146 +15,50 @@ */ package org.couchbase.mock.client; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Map; +import java.io.IOException; +import java.net.InetSocketAddress; /** * Tests for the "extended" Mock API. + * * @author Mark Nunberg */ public class MockAPITest extends ClientBaseTest { + MockHttpClient mockHttpClient; - protected class Command { - Map commandMap = new HashMap(); - Map payload = new HashMap(); - - void setName(String name) { - commandMap.put("command", name); - } - - String getName() { - return (String)commandMap.get("command"); - } - - void set(String param, Object value) { - payload.put(param, value); - } - - void clear() { - commandMap.clear(); - payload.clear(); - commandMap.put("payload", payload); - } - - public Command() { - commandMap.put("payload", payload); - } - } - - private interface CommandSender { - JsonObject transact(Command cmd) throws Exception; + @Override + protected void setUp() throws Exception { + super.setUp(); + mockHttpClient = new MockHttpClient(new InetSocketAddress("localhost", mockClient.getRestPort())); } - - private static Gson gs = new Gson(); - - protected JsonObject jsonLineTxn(Command command) throws Exception { - harakiriOutput.write(gs.toJson(command.commandMap).getBytes()); - harakiriOutput.write('\n'); - - String line = harakiriInput.readLine(); - assertNotNull(line); - - JsonObject ret = gs.fromJson(line, JsonObject.class); - assertNotNull(ret); - - return ret; + public void testEndure() throws IOException { + assertTrue(mockClient.request(new EndureRequest("key", "value", true, 2)).isOk()); + assertTrue(mockHttpClient.request(new EndureRequest("key", "value", true, 2)).isOk()); } - protected JsonObject jsonHttpTxn(Command command) throws Exception { - StringBuilder sb = new StringBuilder(); - sb.append("http://localhost:") - .append(mock.getHttpPort()) - .append("/mock/") - .append(command.getName()) - .append("?"); - - for (Map.Entry kv : command.payload.entrySet()) { - String jStr = gs.toJson(kv.getValue()); - jStr = URLEncoder.encode(jStr, "UTF-8"); - sb.append(kv.getKey()).append('=').append(jStr).append('&'); - } + public void testTimeTravel() throws IOException { + assertTrue(mockClient.request(new TimeTravelRequest(1)).isOk()); + assertTrue(mockClient.request(new TimeTravelRequest(-1)).isOk()); - int index = sb.lastIndexOf("&"); - if (index > 0) { - sb.deleteCharAt(index); + MockResponse res = mockHttpClient.request(new TimeTravelRequest(1)); + if (!res.isOk()) { + System.err.println(res.getErrorMessage()); } - URL url = new URL(sb.toString()); - HttpURLConnection uc = (HttpURLConnection)url.openConnection(); - uc.connect(); - - sb = new StringBuilder(); - byte[] buf = new byte[4096]; - int nr; - while ( (nr = uc.getInputStream().read(buf)) != -1) { - sb.append(new String(buf, 0, nr)); - } - - - return gs.fromJson(sb.toString(), JsonElement.class).getAsJsonObject(); + assertTrue(mockHttpClient.request(new TimeTravelRequest(1)).isOk()); + assertTrue(mockHttpClient.request(new TimeTravelRequest(-1)).isOk()); } - private void doTestSimpleEndure(CommandSender sender) throws Exception { - Command cmd = new Command(); - cmd.setName("help"); - JsonObject response = sender.transact(cmd); - assertTrue(response.has("status")); - - // Try something more complicated - cmd.clear(); - cmd.setName("endure"); - cmd.set("Key", "hello world"); - cmd.set("Value", "new value"); - cmd.set("OnMaster", true); - cmd.set("OnReplicas", 2); - - response = sender.transact(cmd); - assertTrue(response.has("status")); - Object result = client.get("hello world"); - assertNotNull(result); - assertEquals("new value", (String)result); + public void testFailoverRespawn() throws IOException { + assertTrue(mockClient.request(new FailoverRequest(1)).isOk()); + assertTrue(mockClient.request(new RespawnRequest(1)).isOk()); + assertTrue(mockHttpClient.request(new FailoverRequest(1)).isOk()); + assertTrue(mockHttpClient.request(new RespawnRequest(1)).isOk()); } - public void testLineProtocol() throws Exception { - CommandSender sender = new CommandSender() { - - @Override - public JsonObject transact(Command cmd) throws Exception { - return jsonLineTxn(cmd); - } - }; - - doTestSimpleEndure(sender); - } - - - public void testHttpProtocol() throws Exception { - CommandSender sender = new CommandSender() { - - @Override - public JsonObject transact(Command cmd) throws Exception { - return jsonHttpTxn(cmd); - } - }; - - doTestSimpleEndure(sender); + public void testHiccup() throws IOException { + assertTrue(mockClient.request(new HiccupRequest(100, 10)).isOk()); + assertTrue(mockHttpClient.request(new HiccupRequest(1000, 10)).isOk()); } - }