This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kamelets.git


The following commit(s) were added to refs/heads/main by this push:
     new 838ad9185 Fix #542: add a Citrus integration test for couchbase-source 
(#3008)
838ad9185 is described below

commit 838ad91855084de9ad65be0d0f25a102bfc62e7e
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 7 11:37:10 2026 +0200

    Fix #542: add a Citrus integration test for couchbase-source (#3008)
    
    Follow-up to #3007, which shipped the Kamelet without a test because there
    was no Couchbase container in the toolchain. There is one -- it just needs
    setting up by hand.
    
    Citrus 5.0.0 has no built-in Couchbase container type, so this uses the
    generic container with couchbase/server:community-7.6.2. Three details were
    not optional:
    
    - Ports are bound one-to-one. The Couchbase SDK bootstraps from the
      management port and then reconnects using the addresses the cluster
      advertises, so a remapped host port leaves the client unable to reach the
      data service.
    - The wait strategy needs a full-line regex. Testcontainers matches log
      wait patterns against the whole line, so "Starting Couchbase Server" never
      matches and the container times out after five minutes.
    - initCluster.groovy avoids Groovy GStrings entirely. Citrus resolves
      dollar-brace expressions in these scripts as its own test variables, so a
      GString fails with "Unknown variable".
    
    The script initialises the cluster, creates a bucket, adds a primary index
    and seeds one document over plain HTTP, so the test module needs no
    Couchbase SDK on its classpath. It polls for the management port itself
    because the container logs its startup line well before that port serves
    requests.
    
    The statement in application.properties is
    "SELECT META().id AS __id, * FROM _default" rather than the more obvious
    "SELECT * FROM <bucket>". Both parts matter: CouchbaseConsumer skips any
    row without the __id alias, and it runs the query in the bucket scope, so
    the keyspace is the collection.
    
    Passes locally in about 35 seconds:
    
      Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 -- in CouchbaseIT
      SUCCESS (38088ms) couchbase-source-route-test
    
    
    Claude-Session: https://claude.ai/code/session_01Tz352mt7yzGWLfvYTBo2Dk
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../src/test/java/CouchbaseIT.java                 | 33 ++++++++
 .../resources/couchbase/application.properties     | 24 ++++++
 .../couchbase-source-route.citrus.it.yaml          | 60 ++++++++++++++
 .../couchbase/couchbase-source-route.yaml          | 32 ++++++++
 .../test/resources/couchbase/initCluster.groovy    | 96 ++++++++++++++++++++++
 5 files changed, 245 insertions(+)

diff --git a/tests/camel-kamelets-itest/src/test/java/CouchbaseIT.java 
b/tests/camel-kamelets-itest/src/test/java/CouchbaseIT.java
new file mode 100644
index 000000000..101dc584c
--- /dev/null
+++ b/tests/camel-kamelets-itest/src/test/java/CouchbaseIT.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.util.stream.Stream;
+
+import org.citrusframework.api.common.TestLoader;
+import org.citrusframework.junit.jupiter.CitrusSupport;
+import org.citrusframework.junit.jupiter.CitrusTestFactory;
+import org.citrusframework.junit.jupiter.CitrusTestFactorySupport;
+import org.junit.jupiter.api.DynamicTest;
+
+@CitrusSupport
+public class CouchbaseIT {
+
+    @CitrusTestFactory
+    public Stream<DynamicTest> couchbase() {
+        return 
CitrusTestFactorySupport.factory(TestLoader.YAML).packageScan("couchbase");
+    }
+}
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/couchbase/application.properties
 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/application.properties
new file mode 100644
index 000000000..3d426efcb
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/application.properties
@@ -0,0 +1,24 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+# ---------------------------------------------------------------------------
+
+couchbase.protocol=http
+couchbase.hostname=localhost
+couchbase.port=8091
+couchbase.bucket=kamelets
+couchbase.username=Administrator
+couchbase.password=password
+couchbase.statement=SELECT META().id AS __id, * FROM _default
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.citrus.it.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.citrus.it.yaml
new file mode 100644
index 000000000..ccf378dc7
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.citrus.it.yaml
@@ -0,0 +1,60 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+# ---------------------------------------------------------------------------
+
+name: couchbase-source-route-test
+actions:
+  # Create Couchbase infrastructure.
+  #
+  # Citrus has no built-in Couchbase container type, so this uses the generic
+  # one. The ports are bound one-to-one on purpose: the Couchbase SDK 
bootstraps
+  # from the management port and then reconnects using the addresses the 
cluster
+  # advertises, so a remapped host port makes the client unable to reach the
+  # data service.
+  - testcontainers:
+      start:
+        container:
+          name: "couchbase"
+          image: "couchbase/server:community-7.6.2"
+          startUpTimeout: 300
+          portBindings:
+            - "8091:8091"
+            - "8093:8093"
+            - "11210:11210"
+          waitFor:
+            logMessage: ".*Starting Couchbase Server.*"
+
+  # Initialise the cluster, create the bucket and seed one document
+  - groovy:
+      script:
+        file: "couchbase/initCluster.groovy"
+
+  # Create Camel JBang integration (source)
+  - camel:
+      cli:
+        run:
+          waitForRunningState: false
+          integration:
+            file: "couchbase/couchbase-source-route.yaml"
+            systemProperties:
+              file: "couchbase/application.properties"
+
+  # Verify the Couchbase source picked up the seeded document
+  - camel:
+      cli:
+        verify:
+          integration: "couchbase-source-route"
+          logMessage: "hello-from-couchbase"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.yaml
 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.yaml
new file mode 100644
index 000000000..129232091
--- /dev/null
+++ 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/couchbase-source-route.yaml
@@ -0,0 +1,32 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+# ---------------------------------------------------------------------------
+
+- route:
+    from:
+      uri: "kamelet:couchbase-source"
+      parameters:
+        protocol: "{{couchbase.protocol}}"
+        couchbaseHostname: "{{couchbase.hostname}}"
+        couchbasePort: "{{couchbase.port}}"
+        bucket: "{{couchbase.bucket}}"
+        username: "{{couchbase.username}}"
+        password: "{{couchbase.password}}"
+        statement: "{{couchbase.statement}}"
+        delay: 2000
+      steps:
+      - to:
+          uri: "kamelet:log-sink"
diff --git 
a/tests/camel-kamelets-itest/src/test/resources/couchbase/initCluster.groovy 
b/tests/camel-kamelets-itest/src/test/resources/couchbase/initCluster.groovy
new file mode 100644
index 000000000..2ec58c195
--- /dev/null
+++ b/tests/camel-kamelets-itest/src/test/resources/couchbase/initCluster.groovy
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+// Brings a freshly started Couchbase container to the point where the
+// couchbase-source Kamelet can poll it: initialise the cluster, create a
+// bucket, add a primary index and seed one document.
+//
+// Two deliberate constraints:
+//  - plain HTTP rather than the Couchbase SDK, so the test module does not 
need
+//    the SDK on its classpath
+//  - string concatenation rather than Groovy GStrings, because Citrus resolves
+//    dollar-brace expressions in these scripts as its own test variables
+
+def admin = 'http://localhost:8091'
+def query = 'http://localhost:8093/query/service'
+def user = 'Administrator'
+def pass = 'password'
+def bucket = 'kamelets'
+def basic = 'Basic ' + (user + ':' + pass).bytes.encodeBase64().toString()
+
+def call = { String url, String body, boolean auth ->
+    def conn = new URL(url).openConnection()
+    conn.requestMethod = 'POST'
+    conn.doOutput = true
+    conn.connectTimeout = 10000
+    conn.readTimeout = 60000
+    conn.setRequestProperty('Content-Type', 
'application/x-www-form-urlencoded')
+    if (auth) {
+        conn.setRequestProperty('Authorization', basic)
+    }
+    conn.outputStream.withWriter { it << body }
+    def code = conn.responseCode
+    if (code >= 400) {
+        throw new IllegalStateException('POST ' + url + ' failed with ' + code)
+    }
+    return code
+}
+
+// The container logs "Starting Couchbase Server" well before the management
+// port serves requests, so wait for readiness here rather than in the 
container
+// wait strategy.
+def ready = false
+for (int i = 0; i < 60 && !ready; i++) {
+    try {
+        def conn = new URL(admin + '/pools').openConnection()
+        conn.connectTimeout = 2000
+        conn.readTimeout = 2000
+        ready = conn.responseCode == 200
+    } catch (Exception ignored) {
+        // not up yet
+    }
+    if (!ready) {
+        sleep(2000)
+    }
+}
+if (!ready) {
+    throw new IllegalStateException('Couchbase management port did not become 
available at ' + admin)
+}
+
+call(admin + '/pools/default', 'memoryQuota=512&indexMemoryQuota=512', false)
+call(admin + '/node/controller/setupServices', 'services=kv%2Cn1ql%2Cindex', 
false)
+call(admin + '/settings/web', 'port=SAME&username=' + user + '&password=' + 
pass, false)
+call(admin + '/settings/indexes', 'storageMode=forestdb', true)
+call(admin + '/pools/default/buckets', 'name=' + bucket + 
'&ramQuota=128&bucketType=couchbase', true)
+
+// The bucket is created asynchronously and the query service only sees the
+// keyspace once it has warmed up, so retry the index creation until it lands.
+def indexed = false
+for (int i = 0; i < 30 && !indexed; i++) {
+    try {
+        call(query, 'statement=' + URLEncoder.encode('CREATE PRIMARY INDEX ON 
`' + bucket + '`', 'UTF-8'), true)
+        indexed = true
+    } catch (Exception ignored) {
+        sleep(2000)
+    }
+}
+if (!indexed) {
+    throw new IllegalStateException('Could not create the primary index on ' + 
bucket)
+}
+
+def insert = 'INSERT INTO `' + bucket + '` (KEY, VALUE) VALUES ("doc1", 
{"message":"hello-from-couchbase"})'
+call(query, 'statement=' + URLEncoder.encode(insert, 'UTF-8'), true)

Reply via email to