nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: avaje/avaje-inject
Pull request #351 (2023-05-25 22:02:19)

Sanitize DI class imports
On the discord, an issue was reported that something like this generates invalid adapters.

```
@Singleton
public class ServersService {
  ...
  public @Inject ServersService(final @NotNull JedisPool jedisPool, final @NotNull NatsConnection natsConnection) {
    this.jedisPool = jedisPool;
    this.natsConnection = natsConnection;
  }
  ...
```

the generated DI class has annotations in the imports causing compilation to fail.

I could not replicate the issue on my local, but I'm sure this'll fix it. (or else do absolutely nothing).

SentryMan: It seems like it may be an IntelliJ thing. I can't really replicate since I don't have.

Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
index dddc203f..63606e74 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
@@ -303,7 +303,7 @@ final class BeanReader {
     }
     for (String importType : importTypes()) {
       if (Util.validImportType(importType)) {
-        writer.append("import %s;", importType).eol();
+        writer.append("import %s;", Util.sanitizeimports(importType)).eol();
       }
     }
     writer.eol();
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
index d73b8165..b4553422 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
@@ -58,6 +58,17 @@ final class Util {
     return type.substring(0, pos + 1) + type.substring(type.lastIndexOf(' ') + 1);
   }
 
+  public static String sanitizeimports(String type) {
+    int pos = type.indexOf("@");
+    if (pos == -1) {
+      return type.replace("[]", "");
+    }
+
+    var start = pos == 0 ? type.substring(0, pos) : "";
+
+    return start + type.substring(type.lastIndexOf(' ') + 1).replace("[]", "");
+  }
+
   static String nestedPackageOf(String cls) {
     int pos = cls.lastIndexOf('.');
     if (pos < 0) {


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
index a0e75957..85df67af 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
@@ -123,4 +123,10 @@ class UtilTest {
   void trimMethod_when_genericType() {
     assertThat(Util.trimMethod("foo.bar.ProcessMe<java.lang.String>")).isEqualTo("bar_ProcessMe");
   }
+
+  @Test
+  void sanitizedType() {
+    assertEquals("my.Foo", Util.sanitizeimports("my.Foo"));
+    assertEquals("my.Foo", Util.sanitizeimports("@annotationMcgee my.Foo[]"));
+  }
 }
Repository: avaje/avaje-inject
Pull request #352 (2023-05-29 20:21:29)

Rename internal sanitizeImports() method to camel case


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
index 63606e74..e1c86270 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
@@ -303,7 +303,7 @@ final class BeanReader {
     }
     for (String importType : importTypes()) {
       if (Util.validImportType(importType)) {
-        writer.append("import %s;", Util.sanitizeimports(importType)).eol();
+        writer.append("import %s;", Util.sanitizeImports(importType)).eol();
       }
     }
     writer.eol();
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
index b4553422..6176aad9 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
@@ -58,14 +58,12 @@ final class Util {
     return type.substring(0, pos + 1) + type.substring(type.lastIndexOf(' ') + 1);
   }
 
-  public static String sanitizeimports(String type) {
+  public static String sanitizeImports(String type) {
     int pos = type.indexOf("@");
     if (pos == -1) {
       return type.replace("[]", "");
     }
-
     var start = pos == 0 ? type.substring(0, pos) : "";
-
     return start + type.substring(type.lastIndexOf(' ') + 1).replace("[]", "");
   }
 


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
index 85df67af..4914ae9d 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
@@ -125,8 +125,10 @@ class UtilTest {
   }
 
   @Test
-  void sanitizedType() {
-    assertEquals("my.Foo", Util.sanitizeimports("my.Foo"));
-    assertEquals("my.Foo", Util.sanitizeimports("@annotationMcgee my.Foo[]"));
+  void sanitizeImports() {
+    assertEquals("my.Foo", Util.sanitizeImports("my.Foo"));
+    assertEquals("my.Foo", Util.sanitizeImports("my.Foo[]"));
+    assertEquals("my.Foo", Util.sanitizeImports("@annotationMcgee my.Foo[]"));
+    assertEquals("my.Foo", Util.sanitizeImports("@annotationMcgee my.Foo"));
   }
 }
Repository: avaje/avaje-inject
Pull request #357 (2023-06-21 05:54:52)

#356 - Test and refactor for - Fix Generic type Params showing up in generated requires
…


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
index c9e5393b..ec92ed68 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
@@ -401,13 +401,16 @@ final class ScopeInfo {
     writer.append("  public Class<?>[] %s() { return %s; }", fieldName, fieldName).eol();
     writer.append("  private final Class<?>[] %s = new Class<?>[]{", fieldName).eol();
     for (final String rawType : types) {
-      final var pos = rawType.indexOf("<");
-      final var type = pos == -1 ? rawType : rawType.substring(0, pos);
-      writer.append("    %s.class,", type).eol();
+      writer.append("    %s.class,", trimGenerics(rawType)).eol();
     }
     writer.append("  };").eol().eol();
   }
 
+  static String trimGenerics(String rawType) {
+    final var pos = rawType.indexOf("<");
+    return pos == -1 ? rawType : rawType.substring(0, pos);
+  }
+
   void buildAutoProvides(Append writer, Set<String> autoProvides) {
     autoProvides.removeAll(provides);
     if (!autoProvides.isEmpty()) {


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/ScopeInfoTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeInfoTest.java
new file mode 100644
index 00000000..f4037d50
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeInfoTest.java
@@ -0,0 +1,15 @@
+package io.avaje.inject.generator;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ScopeInfoTest {
+
+  @Test
+  void trimGenerics() {
+    assertThat(ScopeInfo.trimGenerics("com.foo.Bar")).isEqualTo("com.foo.Bar");
+    assertThat(ScopeInfo.trimGenerics("com.foo.Bar<String>")).isEqualTo("com.foo.Bar");
+    assertThat(ScopeInfo.trimGenerics("com.foo.Bar<org.other.One>")).isEqualTo("com.foo.Bar");
+  }
+}
Repository: avaje/avaje-inject
Pull request #683 (2024-08-28 11:39:10)

Update parent pom, tidy use argLine in properties
The updated parent pom allows overriding argLine via properties section. We desire this to tidy things up and make it easier to support a EA Valhalla build.


Patch:
diff --git a/inject-generator/pom.xml b/inject-generator/pom.xml
index 1198cb36..883c0961 100644
--- a/inject-generator/pom.xml
+++ b/inject-generator/pom.xml
@@ -49,13 +49,6 @@
 
   <build>
     <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <useModulePath>false</useModulePath>
-        </configuration>
-      </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
diff --git a/inject/pom.xml b/inject/pom.xml
index 6f79db9d..8bfe3542 100644
--- a/inject/pom.xml
+++ b/inject/pom.xml
@@ -102,17 +102,6 @@
         </executions>
       </plugin>
 
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <argLine>
-            --add-opens io.avaje.inject/io.avaje.inject.spi=ALL-UNNAMED
-            --add-opens io.avaje.inject/io.avaje.inject.aop=ALL-UNNAMED
-          </argLine>
-        </configuration>
-      </plugin>
-
     </plugins>
   </build>
 
diff --git a/pom.xml b/pom.xml
index 1db7d971..2432b0ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
   <parent>
     <groupId>org.avaje</groupId>
     <artifactId>java11-oss</artifactId>
-    <version>4.3</version>
+    <version>4.4</version>
   </parent>
   <scm>
     <developerConnection>scm:git:git@github.com:avaje/avaje-inject.git</developerConnection>
@@ -20,6 +20,8 @@
 
   <properties>
     <nexus.staging.autoReleaseAfterClose>true</nexus.staging.autoReleaseAfterClose>
+    <surefire.useModulePath>false</surefire.useModulePath>
+    <argLine>-Dnet.bytebuddy.experimental=true</argLine>
   </properties>
 
   <modules>


Test patch:
diff --git a/blackbox-test-inject/pom.xml b/blackbox-test-inject/pom.xml
index 2e496779..7889b161 100644
--- a/blackbox-test-inject/pom.xml
+++ b/blackbox-test-inject/pom.xml
@@ -12,11 +12,6 @@
   <groupId>io.avaje.blackboxtest</groupId>
   <artifactId>blackbox-test-inject</artifactId>
 
-  <properties>
-    <maven.compiler.release>11</maven.compiler.release>
-    <argLine>-Dnet.bytebuddy.experimental=true</argLine>
-  </properties>
-
   <dependencies>
 
     <dependency>
diff --git a/inject-test/pom.xml b/inject-test/pom.xml
index 0668da93..8bdd4b6b 100644
--- a/inject-test/pom.xml
+++ b/inject-test/pom.xml
@@ -141,14 +141,6 @@
         </configuration>
       </plugin>
 
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <useModulePath>false</useModulePath>
-          <argLine>-Dnet.bytebuddy.experimental=true</argLine>
-        </configuration>
-      </plugin>
     </plugins>
   </build>
 </project>
Repository: avaje/avaje-inject
Pull request #832 (2025-06-13 03:50:36)

Support a main package called `avaje`
now generated module classes will not have the name `AvajeModule` when the package is `avaje`, which clashes with the class of the same name


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
index aefdb320..c18bc9e1 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
@@ -4,7 +4,17 @@ final class ScopeUtil {
 
   static String initName(String name) {
     name = name(name);
-    return "Inject".equals(name) ? "DInject" : name;
+    if (name == null) {
+      return null;
+    }
+    switch (name) {
+      case "Inject":
+        return "DInject";
+      case "Avaje":
+        return "AvajeInject";
+      default:
+        return name;
+    }
   }
 
   static String name(String name) {


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
index cf7e8fec..14403fef 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
@@ -1,8 +1,9 @@
 package io.avaje.inject.generator;
 
-import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
-import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
 
 class ScopeUtilTest {
 
@@ -12,6 +13,9 @@ class ScopeUtilTest {
     assertEquals("Example", ScopeUtil.name("org.Example"));
     assertEquals("Example", ScopeUtil.name("example"));
     assertEquals("Example", ScopeUtil.name("Example"));
+    assertEquals("Example", ScopeUtil.name("ExampleScope"));
+    assertEquals("Example", ScopeUtil.name("ExampleModule"));
+    assertNull(ScopeUtil.name(null));
   }
 
   @Test
@@ -19,6 +23,14 @@ class ScopeUtilTest {
     // resulting module can't be InjectModule as that clashes with @InjectModule
     assertEquals("DInject", ScopeUtil.initName("org.example.inject"));
     assertEquals("Foo", ScopeUtil.initName("org.example.foo"));
+    assertNull(ScopeUtil.initName(null));
+  }
+
+  @Test
+  void initName_avaje() {
+    // resulting module can't be InjectModule as that clashes with @InjectModule
+    assertEquals("AvajeInject", ScopeUtil.initName("org.example.avaje"));
+    assertEquals("Foo", ScopeUtil.initName("org.example.foo"));
   }
 
   @Test
Repository: ably/ably-go
Pull request #149 (2020-04-21 08:35:34)

Don't read from connection on inactive state.
After the connection gets to an inactive state (ie. not CONNECTING,
CONNECTED, CLOSING), no further messages are expected. Instead, moving
back to an active state will start the loop again by calling setConn.


Patch:
diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go
index 238e540..d8def51 100644
--- a/ably/realtime_conn.go
+++ b/ably/realtime_conn.go
@@ -312,6 +312,12 @@ func (c *Conn) isActive() bool {
 	return c.state.current == StateConnConnecting || c.state.current == StateConnConnected
 }
 
+func (c *Conn) lockCanReceiveMessages() bool {
+	c.state.Lock()
+	defer c.state.Unlock()
+	return c.state.current == StateConnConnecting || c.state.current == StateConnConnected || c.state.current == StateConnClosing
+}
+
 func (c *Conn) lockIsActive() bool {
 	c.state.Lock()
 	defer c.state.Unlock()
@@ -330,7 +336,7 @@ func (c *Conn) logger() *LoggerOptions {
 func (c *Conn) eventloop() {
 	var receiveTimeout time.Duration
 
-	for {
+	for c.lockCanReceiveMessages() {
 		var deadline time.Time
 		if receiveTimeout != 0 {
 			deadline = time.Now().Add(receiveTimeout) // RTN23a


Test patch:
diff --git a/ably/realtime_conn_test.go b/ably/realtime_conn_test.go
index a34a524..ffa626a 100644
--- a/ably/realtime_conn_test.go
+++ b/ably/realtime_conn_test.go
@@ -194,3 +194,49 @@ func TestRealtimeConn_ReceiveTimeout(t *testing.T) {
 		t.Fatalf("expected %v, got %v", expected, got)
 	}
 }
+
+func TestRealtimeConn_BreakConnLoopOnInactiveState(t *testing.T) {
+	t.Parallel()
+
+	for _, action := range []proto.Action{
+		proto.ActionDisconnect,
+		proto.ActionError,
+		proto.ActionClosed,
+	} {
+		t.Run(action.String(), func(t *testing.T) {
+			t.Parallel()
+			in := make(chan *proto.ProtocolMessage)
+			out := make(chan *proto.ProtocolMessage, 16)
+
+			app, client := ablytest.NewRealtimeClient(&ably.ClientOptions{
+				Dial: ablytest.MessagePipe(in, out),
+			})
+			defer safeclose(t, app, client)
+
+			connected := &proto.ProtocolMessage{
+				Action:            proto.ActionConnected,
+				ConnectionID:      "connection-id",
+				ConnectionDetails: &proto.ConnectionDetails{},
+			}
+			select {
+			case in <- connected:
+			case <-time.After(10 * time.Millisecond):
+				t.Fatal("didn't receive incoming protocol message")
+			}
+
+			select {
+			case in <- &proto.ProtocolMessage{
+				Action: action,
+			}:
+			case <-time.After(10 * time.Millisecond):
+				t.Fatal("didn't receive incoming protocol message")
+			}
+
+			select {
+			case in <- &proto.ProtocolMessage{}:
+				t.Fatal("called Receive again; expected end of connection loop")
+			case <-time.After(10 * time.Millisecond):
+			}
+		})
+	}
+}
Repository: ably/ably-go
Pull request #169 (2020-07-09 08:03:28)

Add RTN23- heartbeats
- [x] RTN23a
- [x] RTN23b


Patch:
diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go
index 6ffc0ff..b3bf654 100644
--- a/ably/realtime_conn.go
+++ b/ably/realtime_conn.go
@@ -84,6 +84,10 @@ func newConn(opts *ClientOptions, auth *Auth, callbacks connCallbacks) (*Connect
 }
 
 func (c *Connection) dial(proto string, u *url.URL) (proto.Conn, error) {
+	// (RTN23b)
+	query := u.Query()
+	query.Add("heartbeats", "true")
+	u.RawQuery = query.Encode()
 	if c.opts.Dial != nil {
 		return c.opts.Dial(proto, u)
 	}
@@ -476,7 +480,6 @@ func (c *Connection) setSerial(serial int64) {
 
 func (c *Connection) eventloop() {
 	var receiveTimeout time.Duration
-
 	for c.lockCanReceiveMessages() {
 		var deadline time.Time
 		if receiveTimeout != 0 {


Test patch:
diff --git a/ably/realtime_conn_spec_test.go b/ably/realtime_conn_spec_test.go
index 538b20b..e1557f4 100644
--- a/ably/realtime_conn_spec_test.go
+++ b/ably/realtime_conn_spec_test.go
@@ -1078,3 +1078,64 @@ func TestRealtimeConn_RTN16(t *testing.T) {
 func sameConnection(a, b string) bool {
 	return strings.Split(a, "-")[0] == strings.Split(b, "-")[0]
 }
+
+type protoConnWithReceiveHook struct {
+	proto.Conn
+	ok chan *proto.ProtocolMessage
+}
+
+func (w *protoConnWithReceiveHook) Receive(deadline time.Time) (*proto.ProtocolMessage, error) {
+	msg, err := w.Conn.Receive(deadline)
+	w.ok <- msg
+	return msg, err
+}
+
+func TestRealtimeConn_RTN23(t *testing.T) {
+	t.Parallel()
+	var query url.Values
+	ok := make(chan *proto.ProtocolMessage, 2)
+	timeout := 30 * time.Millisecond
+	app, c := ablytest.NewRealtime(&ably.ClientOptions{
+		RealtimeRequestTimeout: timeout,
+		Dial: func(protocol string, u *url.URL) (proto.Conn, error) {
+			query = u.Query()
+			conn, err := ablyutil.DialWebsocket(protocol, u)
+			if err != nil {
+				return nil, err
+			}
+			return &protoConnWithReceiveHook{ok: ok, Conn: conn}, nil
+		},
+	})
+	defer safeclose(t, ablytest.FullRealtimeCloser(c), app)
+
+	err := ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected).Wait()
+	if err != nil {
+		t.Fatal(err)
+	}
+	msg := <-ok // consume the connect message
+	maxIdleInterval := time.Duration(msg.ConnectionDetails.MaxIdleInterval) * time.Millisecond
+	receiveTimeout := timeout + maxIdleInterval
+
+	{ // RTN23b
+		h := query.Get("heartbeats")
+		if h != "true" {
+			t.Errorf("expected heartbeats query param to be true got %q", h)
+		}
+	}
+	{ //RTN23a
+		select {
+		case msg := <-ok:
+			// The test here is ensuring we receive a heartbeat within the timeout bounds
+			// as per rtn23a because the connection is idle since receiving connection
+			// message from ably.
+			if msg == nil {
+				t.Fatal("expected to receive heartbeat message")
+			}
+			if msg.Action != proto.ActionHeartbeat {
+				t.Errorf("expected action %v got %v", proto.ActionHeartbeat, msg.Action)
+			}
+		case <-time.After(receiveTimeout):
+			t.Error("no heartbeat was sent by ably")
+		}
+	}
+}
Repository: ably/ably-go
Pull request #624 (2023-10-03 11:40:21)

Fix message extras unmarshaled to incompatible json type
Message extras should be a json compatible map[string]interface{}, but when marshalling to msgpack and back again, any nested objects become map[interface{}]interface{} which is incompatible with json marshalling.

Set the map type on the msgpack handle to map[string]interface{} so that nested objects are correctly unmarshaled.


Patch:
diff --git a/ably/internal/ablyutil/msgpack.go b/ably/internal/ablyutil/msgpack.go
index 98dd66c..ae19ce1 100644
--- a/ably/internal/ablyutil/msgpack.go
+++ b/ably/internal/ablyutil/msgpack.go
@@ -3,6 +3,7 @@ package ablyutil
 import (
 	"bytes"
 	"io"
+	"reflect"
 
 	"github.com/ugorji/go/codec"
 )
@@ -13,6 +14,7 @@ func init() {
 	handle.Raw = true
 	handle.WriteExt = true
 	handle.RawToString = true
+	handle.MapType = reflect.TypeOf(map[string]interface{}(nil))
 }
 
 // UnmarshalMsgpack decodes the MessagePack-encoded data and stores the result in the


Test patch:
diff --git a/ably/internal/ablyutil/msgpack_test.go b/ably/internal/ablyutil/msgpack_test.go
index 59e98ce..0b35ff0 100644
--- a/ably/internal/ablyutil/msgpack_test.go
+++ b/ably/internal/ablyutil/msgpack_test.go
@@ -5,6 +5,7 @@ package ablyutil
 
 import (
 	"bytes"
+	"encoding/json"
 	"testing"
 )
 
@@ -31,3 +32,28 @@ func TestMsgpack(t *testing.T) {
 		}
 	})
 }
+
+func TestMsgpackJson(t *testing.T) {
+	extras := map[string]interface{}{
+		"headers": map[string]interface{}{
+			"version": 1,
+		},
+	}
+
+	b, err := MarshalMsgpack(extras)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var got map[string]interface{}
+	err = UnmarshalMsgpack(b, &got)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	buff := bytes.Buffer{}
+	err = json.NewEncoder(&buff).Encode(got)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/ably/proto_message_decoding_test.go b/ably/proto_message_decoding_test.go
index 060e740..f96d5a4 100644
--- a/ably/proto_message_decoding_test.go
+++ b/ably/proto_message_decoding_test.go
@@ -107,6 +107,34 @@ func loadMsgpackFixtures() ([]MsgpackTestFixture, error) {
 	return o, err
 }
 
+func TestMsgpackExtrasJsonCompatible(t *testing.T) {
+	p := protocolMessage{
+		Messages: []*Message{
+			{
+				Name: "my event",
+				Data: "hello world",
+				Extras: map[string]interface{}{
+					"headers": map[string]interface{}{
+						"version": 1,
+					},
+				},
+			},
+		},
+	}
+
+	b, err := ablyutil.MarshalMsgpack(p)
+	assert.NoError(t, err)
+
+	var got ProtocolMessage
+	assert.NoError(t, ablyutil.UnmarshalMsgpack(b, &got))
+
+	msg, err := got.Messages[0].withDecodedData(nil)
+	assert.NoError(t, err)
+
+	buff := bytes.Buffer{}
+	assert.NoError(t, json.NewEncoder(&buff).Encode(msg))
+}
+
 func TestMsgpackDecoding(t *testing.T) {
 	msgpackTestFixtures, err := loadMsgpackFixtures()
 	require.NoError(t, err)