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

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


The following commit(s) were added to refs/heads/main by this push:
     new 51d2ceeb4dcb CAMEL-26438: camel-test-dst - mark the fake worlds with 
@DstWorld and list them in the camel-catalog (#28787)
51d2ceeb4dcb is described below

commit 51d2ceeb4dcbd5ca2821850046b67dacc6b5af59
Author: allthingssecurity <[email protected]>
AuthorDate: Sun Nov 1 15:18:47 2026 +0530

    CAMEL-26438: camel-test-dst - mark the fake worlds with @DstWorld and list 
them in the camel-catalog (#28787)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 catalog/camel-catalog/pom.xml                      |   2 +
 .../apache/camel/catalog/components/aws2-sqs.json  |   3 +
 .../camel/catalog/components/azure-eventhubs.json  |   3 +
 .../camel/catalog/components/kafka-share.json      |   3 +
 .../apache/camel/catalog/components/pulsar.json    |   3 +
 .../org/apache/camel/catalog/components/slack.json |   3 +
 .../org/apache/camel/catalog/docs/test-dst.adoc    | 112 ++++--
 .../camel/catalog/CamelCatalogDstWorldsTest.java   |  84 +++++
 .../apache/camel/component/aws2/sqs/aws2-sqs.json  |   3 +
 .../camel/component/aws2/sqs/dst/SqsWorld.java     |  19 ++
 .../component/azure/eventhubs/azure-eventhubs.json |   3 +
 .../azure/eventhubs/dst/EventHubsWorld.java        |  12 +
 .../camel/component/kafka/share/kafka-share.json   |   3 +
 .../component/kafka/share/dst/KafkaShareWorld.java |  23 ++
 .../org/apache/camel/component/pulsar/pulsar.json  |   3 +
 .../camel/component/pulsar/dst/PulsarWorld.java    |  19 ++
 .../org/apache/camel/component/slack/slack.json    |   3 +
 .../camel/component/slack/dst/SlackWorld.java      |  17 +
 .../camel-test-dst/src/main/docs/test-dst.adoc     | 112 ++++--
 .../java/org/apache/camel/test/dst/DstWorld.java   |  64 ++++
 .../modules/ROOT/pages/camel-catalog.adoc          |  31 ++
 .../apache/camel/tooling/model/ComponentModel.java |  11 +
 .../apache/camel/tooling/model/DstWorldModel.java  | 110 ++++++
 .../org/apache/camel/tooling/model/JsonMapper.java |  46 +++
 .../apache/camel/tooling/model/JsonMapperTest.java |  42 +++
 .../maven/packaging/AbstractGenerateMojo.java      |   7 +-
 .../camel/maven/packaging/DstWorldsHelper.java     | 379 +++++++++++++++++++++
 .../packaging/EndpointSchemaGeneratorMojo.java     |  41 +++
 .../maven/packaging/UpdateDstWorldsDocMojo.java    | 178 ++++++++++
 .../camel/maven/packaging/DstWorldsHelperTest.java | 282 +++++++++++++++
 .../packaging/UpdateDstWorldsDocMojoTest.java      | 114 +++++++
 31 files changed, 1695 insertions(+), 40 deletions(-)

diff --git a/catalog/camel-catalog/pom.xml b/catalog/camel-catalog/pom.xml
index 87272a89284d..4bf948bd2c0a 100644
--- a/catalog/camel-catalog/pom.xml
+++ b/catalog/camel-catalog/pom.xml
@@ -179,6 +179,8 @@
                             <goal>update-simple-helper</goal>
                             <!-- update test-infra metadata -->
                             <goal>update-test-infra-metadata</goal>
+                            <!-- update the table of DST worlds in 
camel-test-dst from the catalog -->
+                            <goal>update-dst-worlds-doc</goal>
                         </goals>
                         <phase>generate-resources</phase>
                     </execution>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-sqs.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-sqs.json
index 3f8288b43e5b..ebcc76e86d44 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-sqs.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-sqs.json
@@ -168,5 +168,8 @@
     "useDefaultCredentialsProvider": { "index": 66, "kind": "parameter", 
"displayName": "Use Default Credentials Provider", "group": "security", 
"label": "security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
load c [...]
     "useProfileCredentialsProvider": { "index": 67, "kind": "parameter", 
"displayName": "Use Profile Credentials Provider", "group": "security", 
"label": "security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
load c [...]
     "useSessionCredentials": { "index": 68, "kind": "parameter", 
"displayName": "Use Session Credentials", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
use Session Credentials [...]
+  },
+  "dstWorlds": {
+    "SqsWorld": { "javaType": 
"org.apache.camel.component.aws2.sqs.dst.SqsWorld", "fake": "FakeSqs: a 
standard SQS queue (ReceiveMessage, DeleteMessage, 
ChangeMessageVisibilityBatch) bound as amazonSQSClient", "covers": [ "the 
visibility timeout extender (extendMessageVisibility=true)", "short polling 
with one to three concurrent consumers and up to 10 messages per poll", 
"processing that takes longer than the visibility timeout", "synchronous 
redelivery by the error handler after a dela [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-eventhubs.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-eventhubs.json
index a81217609b1a..8f74b9ef3614 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-eventhubs.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-eventhubs.json
@@ -92,5 +92,8 @@
     "sharedAccessKey": { "index": 23, "kind": "parameter", "displayName": 
"Shared Access Key", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "The generated value for 
the SharedAccessName." },
     "sharedAccessName": { "index": 24, "kind": "parameter", "displayName": 
"Shared Access Name", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "The name you chose for 
your EventHubs SAS keys." },
     "tokenCredential": { "index": 25, "kind": "parameter", "displayName": 
"Token Credential", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": 
"com.azure.core.credential.TokenCredential", "deprecated": false, 
"deprecationNote": "", "autowired": true, "secret": true, "security": "secret", 
"configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "Provide custom  [...]
+  },
+  "dstWorlds": {
+    "EventHubsWorld": { "javaType": 
"org.apache.camel.component.azure.eventhubs.dst.EventHubsWorld", "fake": "a 
mocked EventHubsClientFactory that delivers the events per partition, and a 
fake CheckpointStore", "covers": [ "checkpointing by checkpointBatchSize and 
checkpointBatchTimeout", "one or two partitions with bursts and quiet periods 
of events", "an asynchronous downstream step" ], "faults": [ "the downstream 
step answers slowly", "the downstream step fails", "the checkpoint store [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka-share.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka-share.json
index b3b29cceedcb..4795180d4f60 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka-share.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka-share.json
@@ -192,5 +192,8 @@
     "sslTruststoreLocation": { "index": 71, "kind": "parameter", 
"displayName": "Ssl Truststore Location", "group": "security", "label": 
"common,security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The location of the 
trust store file." },
     "sslTruststorePassword": { "index": 72, "kind": "parameter", 
"displayName": "Ssl Truststore Password", "group": "security", "label": 
"common,security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": true, 
"security": "secret", "configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The password for the 
trust store file. If a p [...]
     "sslTruststoreType": { "index": 73, "kind": "parameter", "displayName": 
"Ssl Truststore Type", "group": "security", "label": "common,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"JKS", "configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The file format of the 
trust store file. The defaul [...]
+  },
+  "dstWorlds": {
+    "KafkaShareWorld": { "javaType": 
"org.apache.camel.component.kafka.share.dst.KafkaShareWorld", "fake": 
"FakeShareGroup: a share group (KIP-932) of one topic with one partition, 
behind ShareConsumer", "covers": [ "explicit acknowledgement with onFailure 
RELEASE or REJECT", "commitMode SYNC and ASYNC", "pollOnError ERROR_HANDLER, 
RECONNECT, RETRY and DISCARD", "acquisition locks and the delivery limit", "the 
backoff when the share consumer cannot be created", "route start, restart, sus 
[...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pulsar.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pulsar.json
index f0c6b696f7f6..f4eafaf76f62 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pulsar.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pulsar.json
@@ -151,5 +151,8 @@
     "producerName": { "index": 48, "kind": "parameter", "displayName": 
"Producer Name", "group": "producer", "label": "producer", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.pulsar.PulsarConfiguration", "configurationField": 
"pulsarConfiguration", "description": "Name of the producer. If unset, lets 
Pulsar select a unique identifier." },
     "sendTimeoutMs": { "index": 49, "kind": "parameter", "displayName": "Send 
Timeout Ms", "group": "producer", "label": "producer", "required": false, 
"type": "integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 30000, "configurationClass": 
"org.apache.camel.component.pulsar.PulsarConfiguration", "configurationField": 
"pulsarConfiguration", "description": "Send timeout in milliseconds" },
     "lazyStartProducer": { "index": 50, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
+  },
+  "dstWorlds": {
+    "PulsarWorld": { "javaType": 
"org.apache.camel.component.pulsar.dst.PulsarWorld", "fake": "FakePulsar: a 
broker and client for one topic and one durable subscription", "covers": [ 
"subscription types Exclusive, Shared, Failover and Key_Shared with one to 
three consumers", "the message listener (messageListener=true, one consumer 
thread)", "the ack timeout and the redelivery of a failed exchange after a 
negative acknowledgement", "route start, restart, suspend and resume", "an 
asynchr [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
index 8fd717ae367a..7f927392152c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
@@ -68,5 +68,8 @@
     "startScheduler": { "index": 27, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
     "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
     "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
+  },
+  "dstWorlds": {
+    "SlackWorld": { "javaType": 
"org.apache.camel.component.slack.dst.SlackWorld", "fake": "a fake Slack Web 
API (conversations.history) returned by a mocked Slack.getInstance", "covers": 
[ "the polling consumer with maxResults and naturalOrder", "the pages of 
conversations.history (oldest and cursor)", "route suspend and resume while a 
batch is routed" ], "faults": [ "the first page of conversations.history 
answers ratelimited", "conversations.history answers slowly", "the route is 
susp [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
index 2e7e4906b15f..20ad229f32b7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
@@ -51,21 +51,64 @@ A `Dst` is one run. The extension creates one per seed and 
closes it after the s
 `new Dst` while another run is open throws `IllegalStateException` ("another 
run (seed N) is still active"), and a DST
 test refuses to start when JUnit runs tests in parallel 
(`junit.jupiter.execution.parallel.enabled=true`).
 
-These worlds are in the Camel code base and are the examples to copy:
+These worlds are in the Camel code base and are the examples to copy. Each 
world class is annotated with `@DstWorld`
+(see step 10 of the guide below), and the build lists it in the `dstWorlds` 
section of the component in the Camel
+catalog. This table is generated from the catalog, which also has the faults 
each world injects and the invariants it
+checks:
 
+// dst worlds: START
+[cols="2,3,3,4,2"]
 |===
-|Component |Test |Regression test for
-
-|camel-aws2-sqs |`SqsVisibilityDstTest` (the visibility timeout extender) 
|CAMEL-25700
-|camel-aws2-sqs |`SqsRedeliveryDstTest` (the extender while Camel's error 
handler waits to redeliver, in virtual
-time) |-
-|camel-azure-eventhubs |`EventHubsCheckpointDstTest` (checkpointing by batch 
size and timeout) |CAMEL-25683
-|camel-kafka-share |`KafkaShareConsumerDstTest` (acknowledgements, acquisition 
locks, reconnects, and suspend and resume
-of the route and of the CamelContext) |CAMEL-25872
-|camel-pulsar |`PulsarConsumerDstTest` (subscribe failures, broker and route 
restarts while the route starts and
-consumes) |CAMEL-25869
-|camel-slack |`SlackSuspendDstTest` (route suspend in the middle of a poll) 
|CAMEL-25684
+|Component |World (tests) |Fake |What it covers |Issues
+
+|camel-aws2-sqs
+|`SqsWorld` (`SqsRedeliveryDstTest`, `SqsVisibilityDstTest`)
+|FakeSqs: a standard SQS queue (ReceiveMessage, DeleteMessage, 
ChangeMessageVisibilityBatch) bound as amazonSQSClient
+a|* the visibility timeout extender (extendMessageVisibility=true)
+* short polling with one to three concurrent consumers and up to 10 messages 
per poll
+* processing that takes longer than the visibility timeout
+* synchronous redelivery by the error handler after a delay longer than the 
visibility timeout
+|CAMEL-25700, CAMEL-25737, CAMEL-25738, CAMEL-25911
+
+|camel-azure-eventhubs
+|`EventHubsWorld` (`EventHubsCheckpointDstTest`)
+|a mocked EventHubsClientFactory that delivers the events per partition, and a 
fake CheckpointStore
+a|* checkpointing by checkpointBatchSize and checkpointBatchTimeout
+* one or two partitions with bursts and quiet periods of events
+* an asynchronous downstream step
+|CAMEL-25683, CAMEL-25738, CAMEL-25781
+
+|camel-kafka-share
+|`KafkaShareWorld` (`KafkaShareConsumerDstTest`)
+|FakeShareGroup: a share group (KIP-932) of one topic with one partition, 
behind ShareConsumer
+a|* explicit acknowledgement with onFailure RELEASE or REJECT
+* commitMode SYNC and ASYNC
+* pollOnError ERROR_HANDLER, RECONNECT, RETRY and DISCARD
+* acquisition locks and the delivery limit
+* the backoff when the share consumer cannot be created
+* route start, restart, suspend and resume
+* suspend and resume of the CamelContext
+|CAMEL-25872, CAMEL-25910
+
+|camel-pulsar
+|`PulsarWorld` (`PulsarConsumerDstTest`)
+|FakePulsar: a broker and client for one topic and one durable subscription
+a|* subscription types Exclusive, Shared, Failover and Key_Shared with one to 
three consumers
+* the message listener (messageListener=true, one consumer thread)
+* the ack timeout and the redelivery of a failed exchange after a negative 
acknowledgement
+* route start, restart, suspend and resume
+* an asynchronous downstream step
+|CAMEL-25869
+
+|camel-slack
+|`SlackWorld` (`SlackSuspendDstTest`)
+|a fake Slack Web API (conversations.history) returned by a mocked 
Slack.getInstance
+a|* the polling consumer with maxResults and naturalOrder
+* the pages of conversations.history (oldest and cursor)
+* route suspend and resume while a batch is routed
+|CAMEL-25684, CAMEL-25738
 |===
+// dst worlds: END
 
 == What the harness controls
 
@@ -334,9 +377,33 @@ A failing seed is not yet a bug. Before reporting one:
 ** a *real bug*: the consumer breaks its contract against a fake that follows 
the documentation.
 . Reproduce a real bug outside DST too, with a plain unit test, before filing 
it.
 
+=== 10. Describe the world with @DstWorld
+
+Annotate the world class (the top-level class named after the file) with 
`@DstWorld`, so tools can find it: the build
+of the component reads the annotation from the test sources and writes the 
world into the `dstWorlds` section of the
+component's JSON in the Camel catalog, with the DST tests of the same package 
that use it, and the table of worlds on
+this page is generated from the catalog.
+
+[source,java]
+----
+@DstWorld(component = "aws2-sqs",
+          fake = "FakeSqs: a standard SQS queue (ReceiveMessage, 
DeleteMessage, ChangeMessageVisibilityBatch) bound as"
+                 + " amazonSQSClient",
+          covers = { "the visibility timeout extender 
(extendMessageVisibility=true)", ... },
+          faults = { "ReceiveMessage fails", "ReceiveMessage answers slowly", 
"DeleteMessage is lost", ... },
+          invariants = { "no message is delivered again while the consumer 
holds it", ... },
+          issues = { "CAMEL-25700", "CAMEL-25737", "CAMEL-25738", 
"CAMEL-25911" })
+final class SqsWorld {
+----
+
+`component` is the scheme of the component (a scheme the module does not have 
fails the build); the invariants are
+named as in `dst.check(...)`; the faults are the ones the world injects, not 
the ones it could. The values must be
+string literals (joined with `+` if needed), as the build reads the source. 
Commit what the build regenerates: the
+component's JSON (built with the component), and its copy in the catalog and 
this page (built with `camel-catalog`).
+
 === Pitfalls seen when agents wrote worlds
 
-Agents wrote nine worlds with this guide. These mistakes came up:
+Agents wrote nine worlds with this guide (not all of them are in the code 
base). These mistakes came up:
 
 * *Timestamps at 0.* Give the times of the fake virtual wall clock time 
(`Dst.EPOCH_MILLIS + dst.now()`, or
   `dst.currentTimeMillis()`), not 0. A Service Bus fake with lock times in 
1970 made the lock renewer renew on every run,
@@ -393,6 +460,7 @@ Later worlds (IMAP, JMS, NATS JetStream, Azure Service Bus) 
showed more:
 * [ ] The component logs at `WARN` in the test log configuration, and 
`target/dst` is cleared between runs.
 * [ ] The invariant states the component's contract.
 * [ ] The world fails on the code before a past fix, passes on `main`, and a 
seed replays with the same SHA-256.
+* [ ] The world class has `@DstWorld`, and the regenerated component JSON and 
catalog are committed.
 
 == Running the tests
 
@@ -407,14 +475,16 @@ DST=components/camel-test/camel-test-dst
 SQS=components/camel-aws/camel-aws2-sqs
 EH=components/camel-azure/camel-azure-eventhubs
 SLACK=components/camel-slack
+SHARE=components/camel-kafka-share
+PULSAR=components/camel-pulsar
 MVN="./mvnw -B -Papache-snapshots test -Dsurefire.failIfNoSpecifiedTests=false 
-Dsurefire.rerunFailingTestsCount=0"
 
 # the harness self-tests
 $MVN -pl $DST -Dtest='Dst*Test'
 
 # the worlds, 1000 seeds each, then 5000 seeds without stopping at the first 
failing one
-$MVN -pl $DST,$SQS,$EH,$SLACK -Dtest='*DstTest'
-$MVN -pl $DST,$SQS,$EH,$SLACK -Dtest='*DstTest' -Ddst.seeds=5000 
-Ddst.maxFailures=100000
+$MVN -pl $DST,$SQS,$EH,$SLACK,$SHARE,$PULSAR -Dtest='*DstTest'
+$MVN -pl $DST,$SQS,$EH,$SLACK,$SHARE,$PULSAR -Dtest='*DstTest' 
-Ddst.seeds=5000 -Ddst.maxFailures=100000
 
 # the self-check: revert the main code of a past fix locally (do not commit), 
run the world, restore
 git show d09593b4a584 -- $SQS/src/main | git apply -R          # CAMEL-25700
@@ -428,10 +498,13 @@ $MVN -pl $DST,$SQS -Dtest=SqsVisibilityDstTest 
-Ddst.seed=8
 diff /tmp/seed-8.first $SQS/target/dst/SqsVisibilityDstTest.*/seed-8.trace && 
echo identical
 ----
 
-The past fixes of the other two worlds are 83aaa80754f9 (CAMEL-25683, Event 
Hubs) and 68329f0919bc (CAMEL-25684,
-Slack). Two system properties belong to a world rather than to the harness: 
`dst.sqs.visibilityTimeouts` (the
-visibility timeouts the SQS world picks from, default `2,3,5,10`; `1` shows 
CAMEL-25737) and `dst.replayCheckSeed` (the
-seed of the in-JVM replay test of the Event Hubs world, default 1).
+The past fixes of the other worlds are 83aaa80754f9 (CAMEL-25683, Event Hubs), 
68329f0919bc (CAMEL-25684, Slack), the
+main code of b62e90ddb2d9 (CAMEL-25869, Pulsar) and the main code of 
20c94435789e (CAMEL-25872, Kafka share, whose world
+was also checked with planted faults). Some system properties belong to a 
world rather than to the harness:
+`dst.sqs.visibilityTimeouts` (the visibility timeouts the SQS world picks 
from, default `2,3,5,10`; `1` shows
+CAMEL-25737), `dst.replayCheckSeed` (the seed of the in-JVM replay test of the 
Event Hubs world, default 1),
+`dst.share.pollOnError` (the pollOnError strategies the Kafka share world 
picks from) and
+`dst.pulsar.subscribeFailRates` (the subscribe failure rates of the Pulsar 
world, `0` runs without subscribe failures).
 
 == Limits
 
@@ -471,6 +544,7 @@ blocking waits go to the loop; `DstClock` is also the 
`TimeSource` provider of C
 not rewritten
 |`DstLedger`, `Invariant`, `Invariants` |what happened to each message, and 
reusable invariants
 |`DstTest`, `DstSeed`, `DstInstrument`, `DstExtension` |the JUnit integration
+|`DstWorld` |the description of a world, which the build lists in the catalog
 |`DstCamel` |a `CamelContext` on the loop, and an asynchronous route step with 
a seeded latency
 |===
 
diff --git 
a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogDstWorldsTest.java
 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogDstWorldsTest.java
new file mode 100644
index 000000000000..0a8fb9714282
--- /dev/null
+++ 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogDstWorldsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+package org.apache.camel.catalog;
+
+import java.util.List;
+
+import org.apache.camel.tooling.model.DstWorldModel;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fake worlds of the deterministic simulation tests in the catalog: the 
dstWorlds section of the components, and
+ * the table of worlds in the camel-test-dst page generated from it 
(CAMEL-26438).
+ */
+class CamelCatalogDstWorldsTest {
+
+    static CamelCatalog catalog;
+
+    @BeforeAll
+    static void createCamelCatalog() {
+        catalog = new DefaultCamelCatalog();
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+            "aws2-sqs,SqsWorld,SqsRedeliveryDstTest SqsVisibilityDstTest",
+            "azure-eventhubs,EventHubsWorld,EventHubsCheckpointDstTest",
+            "kafka-share,KafkaShareWorld,KafkaShareConsumerDstTest",
+            "pulsar,PulsarWorld,PulsarConsumerDstTest",
+            "slack,SlackWorld,SlackSuspendDstTest" })
+    void testComponentWithWorld(String scheme, String world, String tests) {
+        List<DstWorldModel> worlds = 
catalog.componentModel(scheme).getDstWorlds();
+        assertEquals(List.of(world), 
worlds.stream().map(DstWorldModel::getName).toList());
+        DstWorldModel w = worlds.get(0);
+        assertTrue(w.getJavaType().endsWith(".dst." + world), w.getJavaType());
+        assertEquals(List.of(tests.split(" ")), w.getTests());
+        assertTrue(!w.getFake().isBlank() && !w.getCovers().isEmpty() && 
!w.getFaults().isEmpty()
+                && !w.getInvariants().isEmpty() && !w.getIssues().isEmpty(), 
world);
+    }
+
+    @Test
+    void testSqsWorld() {
+        DstWorldModel w = 
catalog.componentModel("aws2-sqs").getDstWorlds().get(0);
+        assertEquals("org.apache.camel.component.aws2.sqs.dst.SqsWorld", 
w.getJavaType());
+        assertTrue(w.getInvariants().contains("no message is delivered again 
while the consumer holds it"),
+                w.getInvariants().toString());
+        assertEquals(List.of("CAMEL-25700", "CAMEL-25737", "CAMEL-25738", 
"CAMEL-25911"), w.getIssues());
+    }
+
+    @Test
+    void testTableOfWorldsInTheDocMatchesTheCatalog() {
+        String doc = catalog.otherAsciiDoc("test-dst");
+        String table = doc.substring(doc.indexOf("// dst worlds: START"), 
doc.indexOf("// dst worlds: END"));
+        List<String> rows = table.lines().filter(l -> 
l.startsWith("|`")).toList();
+        int worlds = 0;
+        for (String name : catalog.findComponentNames()) {
+            for (DstWorldModel w : 
catalog.componentModel(name).getDstWorlds()) {
+                assertTrue(rows.stream().anyMatch(r -> r.startsWith("|`" + 
w.getName() + "`")),
+                        w.getName() + " is not in the table of test-dst.adoc: 
" + rows);
+                worlds++;
+            }
+        }
+        assertEquals(worlds, rows.size(), "the table of test-dst.adoc does not 
match the catalog: " + rows);
+    }
+}
diff --git 
a/components/camel-aws/camel-aws2-sqs/src/generated/resources/META-INF/org/apache/camel/component/aws2/sqs/aws2-sqs.json
 
b/components/camel-aws/camel-aws2-sqs/src/generated/resources/META-INF/org/apache/camel/component/aws2/sqs/aws2-sqs.json
index 3f8288b43e5b..ebcc76e86d44 100644
--- 
a/components/camel-aws/camel-aws2-sqs/src/generated/resources/META-INF/org/apache/camel/component/aws2/sqs/aws2-sqs.json
+++ 
b/components/camel-aws/camel-aws2-sqs/src/generated/resources/META-INF/org/apache/camel/component/aws2/sqs/aws2-sqs.json
@@ -168,5 +168,8 @@
     "useDefaultCredentialsProvider": { "index": 66, "kind": "parameter", 
"displayName": "Use Default Credentials Provider", "group": "security", 
"label": "security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
load c [...]
     "useProfileCredentialsProvider": { "index": 67, "kind": "parameter", 
"displayName": "Use Profile Credentials Provider", "group": "security", 
"label": "security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
load c [...]
     "useSessionCredentials": { "index": 68, "kind": "parameter", 
"displayName": "Use Session Credentials", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": 
"org.apache.camel.component.aws2.sqs.Sqs2Configuration", "configurationField": 
"configuration", "description": "Set whether the SQS client should expect to 
use Session Credentials [...]
+  },
+  "dstWorlds": {
+    "SqsWorld": { "javaType": 
"org.apache.camel.component.aws2.sqs.dst.SqsWorld", "fake": "FakeSqs: a 
standard SQS queue (ReceiveMessage, DeleteMessage, 
ChangeMessageVisibilityBatch) bound as amazonSQSClient", "covers": [ "the 
visibility timeout extender (extendMessageVisibility=true)", "short polling 
with one to three concurrent consumers and up to 10 messages per poll", 
"processing that takes longer than the visibility timeout", "synchronous 
redelivery by the error handler after a dela [...]
   }
 }
diff --git 
a/components/camel-aws/camel-aws2-sqs/src/test/java/org/apache/camel/component/aws2/sqs/dst/SqsWorld.java
 
b/components/camel-aws/camel-aws2-sqs/src/test/java/org/apache/camel/component/aws2/sqs/dst/SqsWorld.java
index 64787fc96ef1..c5f9f17e7015 100644
--- 
a/components/camel-aws/camel-aws2-sqs/src/test/java/org/apache/camel/component/aws2/sqs/dst/SqsWorld.java
+++ 
b/components/camel-aws/camel-aws2-sqs/src/test/java/org/apache/camel/component/aws2/sqs/dst/SqsWorld.java
@@ -32,6 +32,7 @@ import org.apache.camel.test.dst.Dst;
 import org.apache.camel.test.dst.DstCamel;
 import org.apache.camel.test.dst.DstExecutor;
 import org.apache.camel.test.dst.DstLedger;
+import org.apache.camel.test.dst.DstWorld;
 import org.apache.camel.test.dst.Invariants;
 
 /**
@@ -48,6 +49,24 @@ import org.apache.camel.test.dst.Invariants;
  * after a redelivery delay longer than the visibility timeout, on the 
consumer's thread: the consumer holds the message
  * during the wait, and the wait of the error handler (a sleep in Camel core) 
runs in virtual time.
  */
+@DstWorld(component = "aws2-sqs",
+          fake = "FakeSqs: a standard SQS queue (ReceiveMessage, 
DeleteMessage, ChangeMessageVisibilityBatch) bound as"
+                 + " amazonSQSClient",
+          covers = {
+                  "the visibility timeout extender 
(extendMessageVisibility=true)",
+                  "short polling with one to three concurrent consumers and up 
to 10 messages per poll",
+                  "processing that takes longer than the visibility timeout",
+                  "synchronous redelivery by the error handler after a delay 
longer than the visibility timeout" },
+          faults = {
+                  "ReceiveMessage fails", "ReceiveMessage answers slowly", 
"DeleteMessage is lost",
+                  "ChangeMessageVisibilityBatch fails", "processing is slower 
than the visibility timeout",
+                  "processing fails" },
+          invariants = {
+                  "no message is delivered again while the consumer holds it",
+                  "every offered message is routed at least once",
+                  "the queue is drained once the faults cleared",
+                  "a redelivery waits at least the redelivery delay" },
+          issues = { "CAMEL-25700", "CAMEL-25737", "CAMEL-25738", 
"CAMEL-25911" })
 final class SqsWorld {
 
     static final String ROUTE = "sqs";
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/generated/resources/META-INF/org/apache/camel/component/azure/eventhubs/azure-eventhubs.json
 
b/components/camel-azure/camel-azure-eventhubs/src/generated/resources/META-INF/org/apache/camel/component/azure/eventhubs/azure-eventhubs.json
index a81217609b1a..8f74b9ef3614 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/generated/resources/META-INF/org/apache/camel/component/azure/eventhubs/azure-eventhubs.json
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/generated/resources/META-INF/org/apache/camel/component/azure/eventhubs/azure-eventhubs.json
@@ -92,5 +92,8 @@
     "sharedAccessKey": { "index": 23, "kind": "parameter", "displayName": 
"Shared Access Key", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "The generated value for 
the SharedAccessName." },
     "sharedAccessName": { "index": 24, "kind": "parameter", "displayName": 
"Shared Access Name", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "The name you chose for 
your EventHubs SAS keys." },
     "tokenCredential": { "index": 25, "kind": "parameter", "displayName": 
"Token Credential", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": 
"com.azure.core.credential.TokenCredential", "deprecated": false, 
"deprecationNote": "", "autowired": true, "secret": true, "security": "secret", 
"configurationClass": 
"org.apache.camel.component.azure.eventhubs.EventHubsConfiguration", 
"configurationField": "configuration", "description": "Provide custom  [...]
+  },
+  "dstWorlds": {
+    "EventHubsWorld": { "javaType": 
"org.apache.camel.component.azure.eventhubs.dst.EventHubsWorld", "fake": "a 
mocked EventHubsClientFactory that delivers the events per partition, and a 
fake CheckpointStore", "covers": [ "checkpointing by checkpointBatchSize and 
checkpointBatchTimeout", "one or two partitions with bursts and quiet periods 
of events", "an asynchronous downstream step" ], "faults": [ "the downstream 
step answers slowly", "the downstream step fails", "the checkpoint store [...]
   }
 }
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/dst/EventHubsWorld.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/dst/EventHubsWorld.java
index 429dcbbfd975..24f4e9e5b6a1 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/dst/EventHubsWorld.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/dst/EventHubsWorld.java
@@ -45,6 +45,7 @@ import org.apache.camel.test.dst.Dst;
 import org.apache.camel.test.dst.DstCamel;
 import org.apache.camel.test.dst.DstExecutor;
 import org.apache.camel.test.dst.DstLedger;
+import org.apache.camel.test.dst.DstWorld;
 import org.apache.camel.test.dst.Invariant;
 import org.apache.camel.test.dst.Invariants;
 import org.mockito.MockedStatic;
@@ -60,6 +61,17 @@ import static org.mockito.ArgumentMatchers.any;
  * checkpoint store whose answers are delayed, slow or failing. The consumer, 
the route and Camel's scheduler are real;
  * every thread pool runs on the DST loop.
  */
+@DstWorld(component = "azure-eventhubs",
+          fake = "a mocked EventHubsClientFactory that delivers the events per 
partition, and a fake CheckpointStore",
+          covers = {
+                  "checkpointing by checkpointBatchSize and 
checkpointBatchTimeout",
+                  "one or two partitions with bursts and quiet periods of 
events",
+                  "an asynchronous downstream step" },
+          faults = {
+                  "the downstream step answers slowly", "the downstream step 
fails",
+                  "the checkpoint store answers slowly", "the checkpoint store 
fails" },
+          invariants = "every completed message is acknowledged when the 
partition goes quiet",
+          issues = { "CAMEL-25683", "CAMEL-25738", "CAMEL-25781" })
 final class EventHubsWorld {
 
     static final String ROUTE = "eh";
diff --git 
a/components/camel-kafka-share/src/generated/resources/META-INF/org/apache/camel/component/kafka/share/kafka-share.json
 
b/components/camel-kafka-share/src/generated/resources/META-INF/org/apache/camel/component/kafka/share/kafka-share.json
index b3b29cceedcb..4795180d4f60 100644
--- 
a/components/camel-kafka-share/src/generated/resources/META-INF/org/apache/camel/component/kafka/share/kafka-share.json
+++ 
b/components/camel-kafka-share/src/generated/resources/META-INF/org/apache/camel/component/kafka/share/kafka-share.json
@@ -192,5 +192,8 @@
     "sslTruststoreLocation": { "index": 71, "kind": "parameter", 
"displayName": "Ssl Truststore Location", "group": "security", "label": 
"common,security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The location of the 
trust store file." },
     "sslTruststorePassword": { "index": 72, "kind": "parameter", 
"displayName": "Ssl Truststore Password", "group": "security", "label": 
"common,security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": true, 
"security": "secret", "configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The password for the 
trust store file. If a p [...]
     "sslTruststoreType": { "index": 73, "kind": "parameter", "displayName": 
"Ssl Truststore Type", "group": "security", "label": "common,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"JKS", "configurationClass": 
"org.apache.camel.component.kafka.share.KafkaShareConfiguration", 
"configurationField": "configuration", "description": "The file format of the 
trust store file. The defaul [...]
+  },
+  "dstWorlds": {
+    "KafkaShareWorld": { "javaType": 
"org.apache.camel.component.kafka.share.dst.KafkaShareWorld", "fake": 
"FakeShareGroup: a share group (KIP-932) of one topic with one partition, 
behind ShareConsumer", "covers": [ "explicit acknowledgement with onFailure 
RELEASE or REJECT", "commitMode SYNC and ASYNC", "pollOnError ERROR_HANDLER, 
RECONNECT, RETRY and DISCARD", "acquisition locks and the delivery limit", "the 
backoff when the share consumer cannot be created", "route start, restart, sus 
[...]
   }
 }
diff --git 
a/components/camel-kafka-share/src/test/java/org/apache/camel/component/kafka/share/dst/KafkaShareWorld.java
 
b/components/camel-kafka-share/src/test/java/org/apache/camel/component/kafka/share/dst/KafkaShareWorld.java
index 5ec8f75b2665..d7d53f8f6d27 100644
--- 
a/components/camel-kafka-share/src/test/java/org/apache/camel/component/kafka/share/dst/KafkaShareWorld.java
+++ 
b/components/camel-kafka-share/src/test/java/org/apache/camel/component/kafka/share/dst/KafkaShareWorld.java
@@ -35,6 +35,7 @@ import org.apache.camel.test.dst.Dst;
 import org.apache.camel.test.dst.DstCamel;
 import org.apache.camel.test.dst.DstExecutor;
 import org.apache.camel.test.dst.DstLedger;
+import org.apache.camel.test.dst.DstWorld;
 import org.apache.camel.test.dst.Invariants;
 import org.apache.kafka.clients.consumer.AcknowledgeType;
 import org.apache.kafka.clients.consumer.ShareConsumer;
@@ -65,6 +66,28 @@ import org.apache.kafka.clients.consumer.ShareConsumer;
  * thread gives up and the route has no share consumer: once the faults 
cleared, the operator restarts the route, once
  * for each such run of failures.
  */
+@DstWorld(component = "kafka-share",
+          fake = "FakeShareGroup: a share group (KIP-932) of one topic with 
one partition, behind ShareConsumer",
+          covers = {
+                  "explicit acknowledgement with onFailure RELEASE or REJECT",
+                  "commitMode SYNC and ASYNC",
+                  "pollOnError ERROR_HANDLER, RECONNECT, RETRY and DISCARD",
+                  "acquisition locks and the delivery limit",
+                  "the backoff when the share consumer cannot be created",
+                  "route start, restart, suspend and resume",
+                  "suspend and resume of the CamelContext" },
+          faults = {
+                  "the share consumer cannot be created", "poll fails", 
"commit fails",
+                  "the acquisition lock expires under slow processing", 
"processing fails",
+                  "a wakeup arrives while a poll fetches" },
+          invariants = {
+                  "the consumer uses the share consumer as documented",
+                  "the route is started once the faults cleared",
+                  "a started route holds one share consumer",
+                  "every published record completes at least once, or the 
group archived it as it should",
+                  "the share group is drained once the faults cleared",
+                  "no share consumer is left open after the route stopped" },
+          issues = { "CAMEL-25872", "CAMEL-25910" })
 final class KafkaShareWorld {
 
     static final String ROUTE = "share";
diff --git 
a/components/camel-pulsar/src/generated/resources/META-INF/org/apache/camel/component/pulsar/pulsar.json
 
b/components/camel-pulsar/src/generated/resources/META-INF/org/apache/camel/component/pulsar/pulsar.json
index f0c6b696f7f6..f4eafaf76f62 100644
--- 
a/components/camel-pulsar/src/generated/resources/META-INF/org/apache/camel/component/pulsar/pulsar.json
+++ 
b/components/camel-pulsar/src/generated/resources/META-INF/org/apache/camel/component/pulsar/pulsar.json
@@ -151,5 +151,8 @@
     "producerName": { "index": 48, "kind": "parameter", "displayName": 
"Producer Name", "group": "producer", "label": "producer", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.pulsar.PulsarConfiguration", "configurationField": 
"pulsarConfiguration", "description": "Name of the producer. If unset, lets 
Pulsar select a unique identifier." },
     "sendTimeoutMs": { "index": 49, "kind": "parameter", "displayName": "Send 
Timeout Ms", "group": "producer", "label": "producer", "required": false, 
"type": "integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 30000, "configurationClass": 
"org.apache.camel.component.pulsar.PulsarConfiguration", "configurationField": 
"pulsarConfiguration", "description": "Send timeout in milliseconds" },
     "lazyStartProducer": { "index": 50, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
+  },
+  "dstWorlds": {
+    "PulsarWorld": { "javaType": 
"org.apache.camel.component.pulsar.dst.PulsarWorld", "fake": "FakePulsar: a 
broker and client for one topic and one durable subscription", "covers": [ 
"subscription types Exclusive, Shared, Failover and Key_Shared with one to 
three consumers", "the message listener (messageListener=true, one consumer 
thread)", "the ack timeout and the redelivery of a failed exchange after a 
negative acknowledgement", "route start, restart, suspend and resume", "an 
asynchr [...]
   }
 }
diff --git 
a/components/camel-pulsar/src/test/java/org/apache/camel/component/pulsar/dst/PulsarWorld.java
 
b/components/camel-pulsar/src/test/java/org/apache/camel/component/pulsar/dst/PulsarWorld.java
index f3744f947316..9ff1f5d53af5 100644
--- 
a/components/camel-pulsar/src/test/java/org/apache/camel/component/pulsar/dst/PulsarWorld.java
+++ 
b/components/camel-pulsar/src/test/java/org/apache/camel/component/pulsar/dst/PulsarWorld.java
@@ -29,6 +29,7 @@ import org.apache.camel.test.dst.Dst;
 import org.apache.camel.test.dst.DstCamel;
 import org.apache.camel.test.dst.DstExecutor;
 import org.apache.camel.test.dst.DstLedger;
+import org.apache.camel.test.dst.DstWorld;
 import org.apache.camel.test.dst.Invariants;
 
 /**
@@ -44,6 +45,24 @@ import org.apache.camel.test.dst.Invariants;
  * Limits: messageListener=true and numberOfConsumerThreads=1 only (the 
receive() loops of messageListener=false block
  * on the one stack until the run ends); no manual acknowledgement, retry or 
dead letter topic options.
  */
+@DstWorld(component = "pulsar",
+          fake = "FakePulsar: a broker and client for one topic and one 
durable subscription",
+          covers = {
+                  "subscription types Exclusive, Shared, Failover and 
Key_Shared with one to three consumers",
+                  "the message listener (messageListener=true, one consumer 
thread)",
+                  "the ack timeout and the redelivery of a failed exchange 
after a negative acknowledgement",
+                  "route start, restart, suspend and resume",
+                  "an asynchronous downstream step" },
+          faults = {
+                  "subscribe fails (the broker cannot be reached before the 
operation timeout)",
+                  "the broker restarts", "the downstream step answers slowly", 
"the downstream step fails" },
+          invariants = {
+                  "no unexpected client call or consumer count",
+                  "the route is started",
+                  "a started route holds the configured number of Pulsar 
consumers",
+                  "every offered message is routed at least once",
+                  "the subscription is drained once the faults cleared" },
+          issues = "CAMEL-25869")
 final class PulsarWorld {
 
     static final String ROUTE = "pulsar";
diff --git 
a/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
 
b/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
index 8fd717ae367a..7f927392152c 100644
--- 
a/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
+++ 
b/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
@@ -68,5 +68,8 @@
     "startScheduler": { "index": 27, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
     "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
     "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
+  },
+  "dstWorlds": {
+    "SlackWorld": { "javaType": 
"org.apache.camel.component.slack.dst.SlackWorld", "fake": "a fake Slack Web 
API (conversations.history) returned by a mocked Slack.getInstance", "covers": 
[ "the polling consumer with maxResults and naturalOrder", "the pages of 
conversations.history (oldest and cursor)", "route suspend and resume while a 
batch is routed" ], "faults": [ "the first page of conversations.history 
answers ratelimited", "conversations.history answers slowly", "the route is 
susp [...]
   }
 }
diff --git 
a/components/camel-slack/src/test/java/org/apache/camel/component/slack/dst/SlackWorld.java
 
b/components/camel-slack/src/test/java/org/apache/camel/component/slack/dst/SlackWorld.java
index 348a46f4a4c1..11be7541c0cc 100644
--- 
a/components/camel-slack/src/test/java/org/apache/camel/component/slack/dst/SlackWorld.java
+++ 
b/components/camel-slack/src/test/java/org/apache/camel/component/slack/dst/SlackWorld.java
@@ -42,6 +42,7 @@ import org.apache.camel.test.dst.Dst;
 import org.apache.camel.test.dst.DstCamel;
 import org.apache.camel.test.dst.DstExecutor;
 import org.apache.camel.test.dst.DstLedger;
+import org.apache.camel.test.dst.DstWorld;
 import org.apache.camel.test.dst.Invariants;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
@@ -56,6 +57,22 @@ import static org.mockito.ArgumentMatchers.anyString;
  * call, and an operator that suspends and resumes the consumer at seeded 
times (so a suspend can land between two
  * exchanges of one batch). The consumer, its scheduled poll and Camel's 
routing are real.
  */
+@DstWorld(component = "slack",
+          fake = "a fake Slack Web API (conversations.history) returned by a 
mocked Slack.getInstance",
+          covers = {
+                  "the polling consumer with maxResults and naturalOrder",
+                  "the pages of conversations.history (oldest and cursor)",
+                  "route suspend and resume while a batch is routed" },
+          faults = {
+                  "the first page of conversations.history answers 
ratelimited",
+                  "conversations.history answers slowly",
+                  "the route is suspended while an exchange is in a downstream 
call" },
+          invariants = {
+                  "every offered message is routed at least once",
+                  "no message is routed twice",
+                  "route slack is started",
+                  "consumer polls after the faults cleared" },
+          issues = { "CAMEL-25684", "CAMEL-25738" })
 final class SlackWorld {
 
     static final String ROUTE = "slack";
diff --git a/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc 
b/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
index 2e7e4906b15f..20ad229f32b7 100644
--- a/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
+++ b/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
@@ -51,21 +51,64 @@ A `Dst` is one run. The extension creates one per seed and 
closes it after the s
 `new Dst` while another run is open throws `IllegalStateException` ("another 
run (seed N) is still active"), and a DST
 test refuses to start when JUnit runs tests in parallel 
(`junit.jupiter.execution.parallel.enabled=true`).
 
-These worlds are in the Camel code base and are the examples to copy:
+These worlds are in the Camel code base and are the examples to copy. Each 
world class is annotated with `@DstWorld`
+(see step 10 of the guide below), and the build lists it in the `dstWorlds` 
section of the component in the Camel
+catalog. This table is generated from the catalog, which also has the faults 
each world injects and the invariants it
+checks:
 
+// dst worlds: START
+[cols="2,3,3,4,2"]
 |===
-|Component |Test |Regression test for
-
-|camel-aws2-sqs |`SqsVisibilityDstTest` (the visibility timeout extender) 
|CAMEL-25700
-|camel-aws2-sqs |`SqsRedeliveryDstTest` (the extender while Camel's error 
handler waits to redeliver, in virtual
-time) |-
-|camel-azure-eventhubs |`EventHubsCheckpointDstTest` (checkpointing by batch 
size and timeout) |CAMEL-25683
-|camel-kafka-share |`KafkaShareConsumerDstTest` (acknowledgements, acquisition 
locks, reconnects, and suspend and resume
-of the route and of the CamelContext) |CAMEL-25872
-|camel-pulsar |`PulsarConsumerDstTest` (subscribe failures, broker and route 
restarts while the route starts and
-consumes) |CAMEL-25869
-|camel-slack |`SlackSuspendDstTest` (route suspend in the middle of a poll) 
|CAMEL-25684
+|Component |World (tests) |Fake |What it covers |Issues
+
+|camel-aws2-sqs
+|`SqsWorld` (`SqsRedeliveryDstTest`, `SqsVisibilityDstTest`)
+|FakeSqs: a standard SQS queue (ReceiveMessage, DeleteMessage, 
ChangeMessageVisibilityBatch) bound as amazonSQSClient
+a|* the visibility timeout extender (extendMessageVisibility=true)
+* short polling with one to three concurrent consumers and up to 10 messages 
per poll
+* processing that takes longer than the visibility timeout
+* synchronous redelivery by the error handler after a delay longer than the 
visibility timeout
+|CAMEL-25700, CAMEL-25737, CAMEL-25738, CAMEL-25911
+
+|camel-azure-eventhubs
+|`EventHubsWorld` (`EventHubsCheckpointDstTest`)
+|a mocked EventHubsClientFactory that delivers the events per partition, and a 
fake CheckpointStore
+a|* checkpointing by checkpointBatchSize and checkpointBatchTimeout
+* one or two partitions with bursts and quiet periods of events
+* an asynchronous downstream step
+|CAMEL-25683, CAMEL-25738, CAMEL-25781
+
+|camel-kafka-share
+|`KafkaShareWorld` (`KafkaShareConsumerDstTest`)
+|FakeShareGroup: a share group (KIP-932) of one topic with one partition, 
behind ShareConsumer
+a|* explicit acknowledgement with onFailure RELEASE or REJECT
+* commitMode SYNC and ASYNC
+* pollOnError ERROR_HANDLER, RECONNECT, RETRY and DISCARD
+* acquisition locks and the delivery limit
+* the backoff when the share consumer cannot be created
+* route start, restart, suspend and resume
+* suspend and resume of the CamelContext
+|CAMEL-25872, CAMEL-25910
+
+|camel-pulsar
+|`PulsarWorld` (`PulsarConsumerDstTest`)
+|FakePulsar: a broker and client for one topic and one durable subscription
+a|* subscription types Exclusive, Shared, Failover and Key_Shared with one to 
three consumers
+* the message listener (messageListener=true, one consumer thread)
+* the ack timeout and the redelivery of a failed exchange after a negative 
acknowledgement
+* route start, restart, suspend and resume
+* an asynchronous downstream step
+|CAMEL-25869
+
+|camel-slack
+|`SlackWorld` (`SlackSuspendDstTest`)
+|a fake Slack Web API (conversations.history) returned by a mocked 
Slack.getInstance
+a|* the polling consumer with maxResults and naturalOrder
+* the pages of conversations.history (oldest and cursor)
+* route suspend and resume while a batch is routed
+|CAMEL-25684, CAMEL-25738
 |===
+// dst worlds: END
 
 == What the harness controls
 
@@ -334,9 +377,33 @@ A failing seed is not yet a bug. Before reporting one:
 ** a *real bug*: the consumer breaks its contract against a fake that follows 
the documentation.
 . Reproduce a real bug outside DST too, with a plain unit test, before filing 
it.
 
+=== 10. Describe the world with @DstWorld
+
+Annotate the world class (the top-level class named after the file) with 
`@DstWorld`, so tools can find it: the build
+of the component reads the annotation from the test sources and writes the 
world into the `dstWorlds` section of the
+component's JSON in the Camel catalog, with the DST tests of the same package 
that use it, and the table of worlds on
+this page is generated from the catalog.
+
+[source,java]
+----
+@DstWorld(component = "aws2-sqs",
+          fake = "FakeSqs: a standard SQS queue (ReceiveMessage, 
DeleteMessage, ChangeMessageVisibilityBatch) bound as"
+                 + " amazonSQSClient",
+          covers = { "the visibility timeout extender 
(extendMessageVisibility=true)", ... },
+          faults = { "ReceiveMessage fails", "ReceiveMessage answers slowly", 
"DeleteMessage is lost", ... },
+          invariants = { "no message is delivered again while the consumer 
holds it", ... },
+          issues = { "CAMEL-25700", "CAMEL-25737", "CAMEL-25738", 
"CAMEL-25911" })
+final class SqsWorld {
+----
+
+`component` is the scheme of the component (a scheme the module does not have 
fails the build); the invariants are
+named as in `dst.check(...)`; the faults are the ones the world injects, not 
the ones it could. The values must be
+string literals (joined with `+` if needed), as the build reads the source. 
Commit what the build regenerates: the
+component's JSON (built with the component), and its copy in the catalog and 
this page (built with `camel-catalog`).
+
 === Pitfalls seen when agents wrote worlds
 
-Agents wrote nine worlds with this guide. These mistakes came up:
+Agents wrote nine worlds with this guide (not all of them are in the code 
base). These mistakes came up:
 
 * *Timestamps at 0.* Give the times of the fake virtual wall clock time 
(`Dst.EPOCH_MILLIS + dst.now()`, or
   `dst.currentTimeMillis()`), not 0. A Service Bus fake with lock times in 
1970 made the lock renewer renew on every run,
@@ -393,6 +460,7 @@ Later worlds (IMAP, JMS, NATS JetStream, Azure Service Bus) 
showed more:
 * [ ] The component logs at `WARN` in the test log configuration, and 
`target/dst` is cleared between runs.
 * [ ] The invariant states the component's contract.
 * [ ] The world fails on the code before a past fix, passes on `main`, and a 
seed replays with the same SHA-256.
+* [ ] The world class has `@DstWorld`, and the regenerated component JSON and 
catalog are committed.
 
 == Running the tests
 
@@ -407,14 +475,16 @@ DST=components/camel-test/camel-test-dst
 SQS=components/camel-aws/camel-aws2-sqs
 EH=components/camel-azure/camel-azure-eventhubs
 SLACK=components/camel-slack
+SHARE=components/camel-kafka-share
+PULSAR=components/camel-pulsar
 MVN="./mvnw -B -Papache-snapshots test -Dsurefire.failIfNoSpecifiedTests=false 
-Dsurefire.rerunFailingTestsCount=0"
 
 # the harness self-tests
 $MVN -pl $DST -Dtest='Dst*Test'
 
 # the worlds, 1000 seeds each, then 5000 seeds without stopping at the first 
failing one
-$MVN -pl $DST,$SQS,$EH,$SLACK -Dtest='*DstTest'
-$MVN -pl $DST,$SQS,$EH,$SLACK -Dtest='*DstTest' -Ddst.seeds=5000 
-Ddst.maxFailures=100000
+$MVN -pl $DST,$SQS,$EH,$SLACK,$SHARE,$PULSAR -Dtest='*DstTest'
+$MVN -pl $DST,$SQS,$EH,$SLACK,$SHARE,$PULSAR -Dtest='*DstTest' 
-Ddst.seeds=5000 -Ddst.maxFailures=100000
 
 # the self-check: revert the main code of a past fix locally (do not commit), 
run the world, restore
 git show d09593b4a584 -- $SQS/src/main | git apply -R          # CAMEL-25700
@@ -428,10 +498,13 @@ $MVN -pl $DST,$SQS -Dtest=SqsVisibilityDstTest 
-Ddst.seed=8
 diff /tmp/seed-8.first $SQS/target/dst/SqsVisibilityDstTest.*/seed-8.trace && 
echo identical
 ----
 
-The past fixes of the other two worlds are 83aaa80754f9 (CAMEL-25683, Event 
Hubs) and 68329f0919bc (CAMEL-25684,
-Slack). Two system properties belong to a world rather than to the harness: 
`dst.sqs.visibilityTimeouts` (the
-visibility timeouts the SQS world picks from, default `2,3,5,10`; `1` shows 
CAMEL-25737) and `dst.replayCheckSeed` (the
-seed of the in-JVM replay test of the Event Hubs world, default 1).
+The past fixes of the other worlds are 83aaa80754f9 (CAMEL-25683, Event Hubs), 
68329f0919bc (CAMEL-25684, Slack), the
+main code of b62e90ddb2d9 (CAMEL-25869, Pulsar) and the main code of 
20c94435789e (CAMEL-25872, Kafka share, whose world
+was also checked with planted faults). Some system properties belong to a 
world rather than to the harness:
+`dst.sqs.visibilityTimeouts` (the visibility timeouts the SQS world picks 
from, default `2,3,5,10`; `1` shows
+CAMEL-25737), `dst.replayCheckSeed` (the seed of the in-JVM replay test of the 
Event Hubs world, default 1),
+`dst.share.pollOnError` (the pollOnError strategies the Kafka share world 
picks from) and
+`dst.pulsar.subscribeFailRates` (the subscribe failure rates of the Pulsar 
world, `0` runs without subscribe failures).
 
 == Limits
 
@@ -471,6 +544,7 @@ blocking waits go to the loop; `DstClock` is also the 
`TimeSource` provider of C
 not rewritten
 |`DstLedger`, `Invariant`, `Invariants` |what happened to each message, and 
reusable invariants
 |`DstTest`, `DstSeed`, `DstInstrument`, `DstExtension` |the JUnit integration
+|`DstWorld` |the description of a world, which the build lists in the catalog
 |`DstCamel` |a `CamelContext` on the loop, and an asynchronous route step with 
a seeded latency
 |===
 
diff --git 
a/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstWorld.java
 
b/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstWorld.java
new file mode 100644
index 000000000000..63b9e6ac70c5
--- /dev/null
+++ 
b/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstWorld.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package org.apache.camel.test.dst;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Describes a fake world of a component: put it on the world class, the 
top-level class named after its file (for
+ * example {@code SqsWorld} in {@code SqsWorld.java}), in the test sources of 
the component.
+ * <p>
+ * The worlds are test classes, so they never reach the component JAR. When 
the component is built, the
+ * camel-package-maven-plugin reads this annotation from the test sources (not 
from the compiled class) and writes the
+ * world into the {@code dstWorlds} section of the component's JSON in the 
catalog, together with the DST tests in the
+ * package of the world that use it. The table of worlds in the camel-test-dst 
documentation is generated from the
+ * catalog. Because the plugin reads the source, every value must be a string 
literal, a concatenation of string
+ * literals with {@code +}, or an array of those; a constant fails the build.
+ *
+ * @since 4.24
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface DstWorld {
+
+    /**
+     * The scheme of the component whose consumer the world runs, for example 
{@code aws2-sqs}. In a module with several
+     * components, this picks the JSON the world is written to; a scheme the 
module does not have fails the build.
+     */
+    String component();
+
+    /** The fake of the external system: the class name of the fake, or how 
the client is faked if there is no class. */
+    String fake();
+
+    /** What the world exercises: the behaviours and options of the component, 
and the operations of the load. */
+    String[] covers() default {};
+
+    /** The faults the world injects (with the seed choosing when and how 
often). */
+    String[] faults() default {};
+
+    /** The invariants checked at the end of each run, named as in {@code 
dst.check(...)}. */
+    String[] invariants() default {};
+
+    /**
+     * The JIRA issues of the world: the issue that added it, the bugs it is a 
regression test for and the bugs it
+     * found, for example {@code CAMEL-25700}.
+     */
+    String[] issues() default {};
+}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
index 864526f0844e..5b231a985486 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc
@@ -156,6 +156,37 @@ out.getJavaType();     // java.lang.String
 See xref:body-types.adoc[Message Body Types] for what the body types are, the 
tools that use them, how components
 declare them, and the JSON of each side with the rule to read it for the 
options of a step.
 
+=== Simulation test worlds in the Catalog
+
+A component whose consumer has a fake world for the deterministic simulation 
tests of the `camel-test-dst` module has a
+`dstWorlds` section in the catalog, with an entry per world class: the fake of 
the external system, what the world
+covers, the faults it injects, the invariants it checks, its JIRA issues and 
the DST tests that run it. The worlds are
+test classes, so this is how tools (and AI agents asked to test a consumer) 
find out which components have a world, and
+which have none yet. The entries come from the `@DstWorld` annotation on each 
world class; `javaType` is a test class,
+which is in no JAR:
+
+[source,json]
+----
+"dstWorlds": {
+  "SqsWorld": { "javaType": "org.apache.camel.component.aws2.sqs.dst.SqsWorld",
+    "fake": "FakeSqs: a standard SQS queue (ReceiveMessage, DeleteMessage, 
ChangeMessageVisibilityBatch) bound as amazonSQSClient",
+    "covers": [ "the visibility timeout extender 
(extendMessageVisibility=true)", ... ],
+    "faults": [ "ReceiveMessage fails", ... ], "invariants": [ ... ],
+    "issues": [ "CAMEL-25700", "CAMEL-25737", "CAMEL-25738", "CAMEL-25911" ],
+    "tests": [ "SqsRedeliveryDstTest", "SqsVisibilityDstTest" ] }
+}
+----
+
+The Java API gives them on the model:
+
+[source,java]
+----
+CamelCatalog catalog = new DefaultCamelCatalog();
+
+List<DstWorldModel> worlds = catalog.componentModel("aws2-sqs").getDstWorlds();
+worlds.get(0).getTests();     // [SqsRedeliveryDstTest, SqsVisibilityDstTest]
+----
+
 === Documentation in the Catalog
 
 The catalog includes the AsciiDoc documentation pages from all modules. This 
makes the full
diff --git 
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
index bfce0ec3818b..a6042a8377fa 100644
--- 
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
+++ 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
@@ -43,6 +43,8 @@ public class ComponentModel extends 
ArtifactModel<ComponentModel.ComponentOption
     protected final List<EndpointHeaderModel> headers = new ArrayList<>();
     // lets sort apis A..Z so they are always in the same order
     protected final Collection<ApiModel> apiOptions = new 
TreeSet<>(Comparators.apiModelComparator());
+    // the fake worlds of the component's deterministic simulation tests, by 
name (CAMEL-26438)
+    protected final List<DstWorldModel> dstWorlds = new ArrayList<>();
 
     public ComponentModel() {
     }
@@ -216,6 +218,15 @@ public class ComponentModel extends 
ArtifactModel<ComponentModel.ComponentOption
         return apiOptions;
     }
 
+    /** The fake worlds of the deterministic simulation tests (camel-test-dst) 
of the component. */
+    public List<DstWorldModel> getDstWorlds() {
+        return dstWorlds;
+    }
+
+    public void addDstWorld(DstWorldModel world) {
+        dstWorlds.add(world);
+    }
+
     public static class EndpointHeaderModel extends BaseOptionModel {
 
         /**
diff --git 
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DstWorldModel.java
 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DstWorldModel.java
new file mode 100644
index 000000000000..6d15d4c3fb7f
--- /dev/null
+++ 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DstWorldModel.java
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+package org.apache.camel.tooling.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fake world of a component for deterministic simulation tests 
(camel-test-dst), as declared with {@code @DstWorld}
+ * on the world class in the test sources of the component (CAMEL-26438).
+ *
+ * @since 4.24
+ */
+public class DstWorldModel {
+
+    private String name;
+    private String javaType;
+    private String fake;
+    private List<String> covers = new ArrayList<>();
+    private List<String> faults = new ArrayList<>();
+    private List<String> invariants = new ArrayList<>();
+    private List<String> issues = new ArrayList<>();
+    private List<String> tests = new ArrayList<>();
+
+    /** The simple name of the world class, for example {@code SqsWorld}. */
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /** The fully qualified name of the world class (a test class: it is not 
in the component JAR). */
+    public String getJavaType() {
+        return javaType;
+    }
+
+    public void setJavaType(String javaType) {
+        this.javaType = javaType;
+    }
+
+    /** The fake of the external system: the class name of the fake, or how 
the client is faked. */
+    public String getFake() {
+        return fake;
+    }
+
+    public void setFake(String fake) {
+        this.fake = fake;
+    }
+
+    /** What the world exercises. */
+    public List<String> getCovers() {
+        return covers;
+    }
+
+    public void setCovers(List<String> covers) {
+        this.covers = covers;
+    }
+
+    /** The faults the world injects. */
+    public List<String> getFaults() {
+        return faults;
+    }
+
+    public void setFaults(List<String> faults) {
+        this.faults = faults;
+    }
+
+    /** The invariants checked at the end of each run. */
+    public List<String> getInvariants() {
+        return invariants;
+    }
+
+    public void setInvariants(List<String> invariants) {
+        this.invariants = invariants;
+    }
+
+    /** The JIRA issues of the world. */
+    public List<String> getIssues() {
+        return issues;
+    }
+
+    public void setIssues(List<String> issues) {
+        this.issues = issues;
+    }
+
+    /** The simple names of the DST test classes that run the world. */
+    public List<String> getTests() {
+        return tests;
+    }
+
+    public void setTests(List<String> tests) {
+        this.tests = tests;
+    }
+}
diff --git 
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
index c38811ebb76a..2011835461ad 100644
--- 
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
+++ 
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
@@ -157,6 +157,7 @@ public final class JsonMapper {
                 }
             }
         }
+        parseDstWorlds(obj.getMap("dstWorlds"), model);
         mprap = (JsonObject) obj.get("apiProperties");
         if (mprap != null) {
             for (Map.Entry<String, Object> entry : mprap.entrySet()) {
@@ -261,9 +262,54 @@ public final class JsonMapper {
             wrapper.put("apis", apiModelAsJsonObject(model.getApiOptions(), 
false));
             wrapper.put("apiProperties", 
apiModelAsJsonObject(model.getApiOptions(), true));
         }
+        if (!model.getDstWorlds().isEmpty()) {
+            wrapper.put("dstWorlds", 
asJsonObjectDstWorlds(model.getDstWorlds()));
+        }
         return wrapper;
     }
 
+    /** The fake worlds of the deterministic simulation tests of a component, 
by the simple name of the world class. */
+    private static void parseDstWorlds(JsonObject json, ComponentModel model) {
+        if (json == null) {
+            return;
+        }
+        for (Map.Entry<String, Object> entry : json.entrySet()) {
+            JsonObject mp = (JsonObject) entry.getValue();
+            DstWorldModel world = new DstWorldModel();
+            world.setName(entry.getKey());
+            world.setJavaType(mp.getString("javaType"));
+            world.setFake(mp.getString("fake"));
+            world.setCovers(stringList(mp.getCollection("covers")));
+            world.setFaults(stringList(mp.getCollection("faults")));
+            world.setInvariants(stringList(mp.getCollection("invariants")));
+            world.setIssues(stringList(mp.getCollection("issues")));
+            world.setTests(stringList(mp.getCollection("tests")));
+            model.addDstWorld(world);
+        }
+    }
+
+    /**
+     * The fake worlds of a component as the catalog has them: an object per 
world, by the simple name of the world
+     * class, with its class, its fake, what it covers, the faults it injects, 
the invariants it checks, its JIRA issues
+     * and the DST tests that run it (CAMEL-26438).
+     */
+    public static JsonObject asJsonObjectDstWorlds(List<DstWorldModel> worlds) 
{
+        JsonObject json = new JsonObject();
+        for (DstWorldModel world : worlds) {
+            JsonObject jo = new JsonObject();
+            jo.put("javaType", world.getJavaType());
+            jo.put("fake", world.getFake());
+            jo.put("covers", world.getCovers());
+            jo.put("faults", world.getFaults());
+            jo.put("invariants", world.getInvariants());
+            jo.put("issues", world.getIssues());
+            jo.put("tests", world.getTests());
+            jo.entrySet().removeIf(e -> e.getValue() == null);
+            json.put(world.getName(), jo);
+        }
+        return json;
+    }
+
     public static DataFormatModel generateDataFormatModel(String json) {
         JsonObject obj = deserialize(json);
         return generateDataFormatModel(obj);
diff --git 
a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
 
b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
index 2c23a4a9c3f1..62d5c0b1b1c8 100644
--- 
a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
+++ 
b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
@@ -399,4 +399,46 @@ class JsonMapperTest {
         assertEquals("long", some.getLanguageOptions().get(0).getJavaType());
         assertEquals(List.of("resultType"), 
some.getOptions().stream().map(BaseOptionModel::getName).toList());
     }
+
+    @Test
+    void testShouldReadAndWriteDstWorlds() {
+        // CAMEL-26438: the fake worlds of a component's deterministic 
simulation tests
+        ComponentModel model = new ComponentModel();
+        model.setName("aws2-sqs");
+        model.setScheme("aws2-sqs");
+        String json = JsonMapper.createParameterJsonSchema(model);
+        assertFalse(json.contains("\"dstWorlds\""), json);
+        
assertTrue(JsonMapper.generateComponentModel(json).getDstWorlds().isEmpty());
+
+        DstWorldModel world = new DstWorldModel();
+        world.setName("SqsWorld");
+        world.setJavaType("org.apache.camel.component.aws2.sqs.dst.SqsWorld");
+        world.setFake("FakeSqs");
+        world.setCovers(List.of("the visibility timeout extender"));
+        world.setFaults(List.of("ReceiveMessage fails", "DeleteMessage is 
lost"));
+        world.setInvariants(List.of("every offered message is routed at least 
once"));
+        world.setIssues(List.of("CAMEL-25700"));
+        world.setTests(List.of("SqsRedeliveryDstTest", 
"SqsVisibilityDstTest"));
+        model.addDstWorld(world);
+
+        json = JsonMapper.createParameterJsonSchema(model);
+        // a section of its own after the options, one line per world
+        assertTrue(json.indexOf("\"dstWorlds\"") > 
json.indexOf("\"properties\""), json);
+        assertTrue(json.contains("\"SqsWorld\": { \"javaType\": 
\"org.apache.camel.component.aws2.sqs.dst.SqsWorld\","),
+                json);
+        ComponentModel model2 = JsonMapper.generateComponentModel(json);
+        assertEquals(1, model2.getDstWorlds().size());
+        DstWorldModel world2 = model2.getDstWorlds().get(0);
+        assertEquals("SqsWorld", world2.getName());
+        assertEquals("org.apache.camel.component.aws2.sqs.dst.SqsWorld", 
world2.getJavaType());
+        assertEquals("FakeSqs", world2.getFake());
+        assertEquals(List.of("the visibility timeout extender"), 
world2.getCovers());
+        assertEquals(List.of("ReceiveMessage fails", "DeleteMessage is lost"), 
world2.getFaults());
+        assertEquals(List.of("every offered message is routed at least once"), 
world2.getInvariants());
+        assertEquals(List.of("CAMEL-25700"), world2.getIssues());
+        assertEquals(List.of("SqsRedeliveryDstTest", "SqsVisibilityDstTest"), 
world2.getTests());
+        // the section is written again as it was read
+        String json2 = JsonMapper.createParameterJsonSchema(model2);
+        assertEquals(json.substring(json.indexOf("\"dstWorlds\"")), 
json2.substring(json2.indexOf("\"dstWorlds\"")));
+    }
 }
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/AbstractGenerateMojo.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/AbstractGenerateMojo.java
index a495b899b309..b4ed8fce642c 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/AbstractGenerateMojo.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/AbstractGenerateMojo.java
@@ -128,10 +128,13 @@ public abstract class AbstractGenerateMojo extends 
AbstractMojo {
             final String prvdata = getPreviousRunData(cacheData);
             if (INCREMENTAL_DATA.equals(prvdata)) {
                 long lastmod = Files.getLastModifiedTime(cacheData).toMillis();
-                Set<String> stale = Stream.concat(Stream.concat(
+                // the test sources too: the @DstWorld classes there go into 
the component JSON (CAMEL-26438)
+                Set<String> stale = Stream.of(
                         
project.getCompileSourceRoots().stream().map(File::new),
-                        Stream.of(new 
File(project.getBuild().getOutputDirectory()))),
+                        
project.getTestCompileSourceRoots().stream().map(File::new),
+                        Stream.of(new 
File(project.getBuild().getOutputDirectory())),
                         project.getArtifacts().stream().map(Artifact::getFile))
+                        .flatMap(f -> f)
                         .flatMap(f -> newer(lastmod, 
f)).collect(Collectors.toSet());
                 if (!stale.isEmpty()) {
                     getLog().info("Stale files detected, re-generating.");
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/DstWorldsHelper.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/DstWorldsHelper.java
new file mode 100644
index 000000000000..c043f30b8cc3
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/DstWorldsHelper.java
@@ -0,0 +1,379 @@
+/*
+ * 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.
+ */
+package org.apache.camel.maven.packaging;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.apache.camel.tooling.model.DstWorldModel;
+import org.jboss.forge.roaster.Roaster;
+import org.jboss.forge.roaster.model.JavaType;
+import org.jboss.forge.roaster.model.JavaUnit;
+import org.jboss.forge.roaster.model.source.AnnotationSource;
+import org.jboss.forge.roaster.model.source.Importer;
+import org.jboss.forge.roaster.model.source.JavaClassSource;
+import org.jboss.forge.roaster.model.source.JavaSource;
+import org.jboss.forge.roaster.model.source.TypeHolderSource;
+
+/**
+ * Finds the fake worlds of the deterministic simulation tests of a component: 
the classes of its test sources annotated
+ * with {@code org.apache.camel.test.dst.DstWorld} (CAMEL-26438).
+ * <p>
+ * The worlds are test classes, which are not compiled when the component JSON 
is generated (and not at all with
+ * {@code -Dmaven.test.skip}), so the annotation is read from the source. Its 
values must be string literals,
+ * concatenations of string literals or arrays of those.
+ */
+public final class DstWorldsHelper {
+
+    static final String DST_PACKAGE = "org.apache.camel.test.dst";
+    static final String DST_WORLD = DST_PACKAGE + ".DstWorld";
+
+    // the annotation used in a source, and the ways a source can refer to the 
one of camel-test-dst
+    private static final Pattern ANNOTATED = 
Pattern.compile("@\\s*(org\\.apache\\.camel\\.test\\.dst\\.)?DstWorld\\b");
+    private static final Pattern OURS = Pattern.compile(
+            
"org\\.apache\\.camel\\.test\\.dst\\.DstWorld\\b|import\\s+org\\.apache\\.camel\\.test\\.dst\\.\\*"
+                                                        + 
"|package\\s+org\\.apache\\.camel\\.test\\.dst\\s*;");
+
+    private DstWorldsHelper() {
+    }
+
+    /**
+     * The worlds in the given test source roots, by the scheme of the 
component they name, each list sorted by the
+     * simple name of the world class.
+     *
+     * @throws IllegalArgumentException if a file that uses the annotation 
cannot be parsed, an annotation is invalid or
+     *                                  not on the top-level class of its 
file, or two worlds have the same simple name
+     */
+    public static Map<String, List<DstWorldModel>> findDstWorlds(List<Path> 
testSourceRoots) throws IOException {
+        Map<String, List<DstWorldModel>> answer = new TreeMap<>();
+        Map<String, String> names = new HashMap<>();
+        for (Path root : testSourceRoots.stream().map(p -> 
p.toAbsolutePath().normalize()).distinct().toList()) {
+            if (!Files.isDirectory(root)) {
+                continue;
+            }
+            List<Path> files;
+            try (Stream<Path> walk = Files.walk(root)) {
+                files = 
walk.filter(DstWorldsHelper::isJavaFile).sorted().toList();
+            }
+            for (Path file : files) {
+                String text = read(file);
+                // cheap checks first: most test classes are not worlds; the 
parsed types decide
+                if (!text.contains("DstWorld") || 
!ANNOTATED.matcher(text).find() || !OURS.matcher(text).find()) {
+                    continue;
+                }
+                AnnotationSource<JavaClassSource> annotation = 
annotation(text, file);
+                if (annotation == null) {
+                    continue;
+                }
+                String component = value(annotation, "component", file);
+                DstWorldModel world = world(annotation, file);
+                String other = names.put(world.getName(), world.getJavaType());
+                if (other != null) {
+                    throw new IllegalArgumentException(
+                            "Two @DstWorld classes have the same simple name: 
" + other + " and " + world.getJavaType());
+                }
+                world.setTests(findTests(file, world.getName()));
+                answer.computeIfAbsent(component, k -> new 
ArrayList<>()).add(world);
+            }
+        }
+        answer.values().forEach(l -> 
l.sort(Comparator.comparing(DstWorldModel::getName)));
+        return answer;
+    }
+
+    /** The world declared by the annotation on its class. */
+    static DstWorldModel world(AnnotationSource<JavaClassSource> annotation, 
Path file) {
+        JavaClassSource clazz = annotation.getOrigin();
+        DstWorldModel world = new DstWorldModel();
+        world.setName(clazz.getName());
+        world.setJavaType(clazz.getQualifiedName());
+        world.setFake(value(annotation, "fake", file));
+        world.setCovers(values(annotation, "covers", file));
+        world.setFaults(values(annotation, "faults", file));
+        world.setInvariants(values(annotation, "invariants", file));
+        world.setIssues(values(annotation, "issues", file));
+        return world;
+    }
+
+    /**
+     * The {@code @DstWorld} of the top-level class of a source, or null when 
no type of the source has the one of
+     * camel-test-dst (the text only mentions it, in a comment or a string, or 
the type has another annotation of that
+     * name, imported by name). A world anywhere else (on a record, interface, 
enum, nested class or another class of
+     * the file than the one named after the file) would be left out of the 
catalog, so it fails; so does an annotation
+     * named {@code DstWorld} that the build cannot tell apart from the one of 
camel-test-dst.
+     */
+    private static AnnotationSource<JavaClassSource> annotation(String text, 
Path file) {
+        JavaUnit unit;
+        try {
+            unit = Roaster.parseUnit(text);
+        } catch (RuntimeException e) {
+            throw new IllegalArgumentException("Cannot parse " + file + " for 
@DstWorld: " + e.getMessage(), e);
+        }
+        JavaType<?> governing = unit.getGoverningType();
+        String packagePrefix = governing.getPackage() == null ? "" : 
governing.getPackage() + ".";
+        // Roaster resolves a type of a file with two or more on-demand 
imports to the file's own package
+        boolean dstStarImport = governing instanceof Importer<?> importer
+                && importer.getImports().stream()
+                        .anyMatch(i -> i.isWildcard() && !i.isStatic() && 
DST_PACKAGE.equals(i.getPackage()));
+        List<JavaSource<?>> annotated = new ArrayList<>();
+        for (JavaType<?> type : unit.getTopLevelTypes()) {
+            if (type instanceof JavaSource<?> source) {
+                collectAnnotated(source, packagePrefix, dstStarImport, 
annotated, file);
+            }
+        }
+        if (annotated.isEmpty()) {
+            return null;
+        }
+        String fileName = file.getFileName().toString();
+        String fileType = fileName.substring(0, fileName.length() - 
".java".length());
+        if (annotated.size() == 1 && annotated.get(0) instanceof 
JavaClassSource clazz
+                && clazz.getQualifiedName().equals(packagePrefix + fileType)
+                && unit.getTopLevelTypes().stream().anyMatch(t -> 
t.getQualifiedName().equals(clazz.getQualifiedName()))) {
+            for (AnnotationSource<JavaClassSource> a : clazz.getAnnotations()) 
{
+                if (isDstWorld(a, packagePrefix, dstStarImport)) {
+                    return a;
+                }
+            }
+        }
+        throw new IllegalArgumentException(
+                "@DstWorld in " + file + " must be on the top-level class 
named after the file (" + fileType
+                                           + "), not on a record, interface, 
enum, nested class or another class of the"
+                                           + " file");
+    }
+
+    private static void collectAnnotated(
+            JavaSource<?> type, String packagePrefix, boolean dstStarImport, 
List<JavaSource<?>> annotated, Path file) {
+        for (AnnotationSource<?> a : type.getAnnotations()) {
+            if (isDstWorld(a, packagePrefix, dstStarImport)) {
+                annotated.add(type);
+                break;
+            }
+            if ("DstWorld".equals(a.getName()) && 
a.getQualifiedName().equals(packagePrefix + "DstWorld")) {
+                // not imported by name nor from camel-test-dst: the build 
cannot tell which annotation it is
+                throw new IllegalArgumentException(
+                        "@DstWorld on " + type.getName() + " in " + file + " 
is not resolved to " + DST_WORLD
+                                                   + ": import " + DST_WORLD + 
" (or " + DST_PACKAGE + ".*)");
+            }
+        }
+        if (type instanceof TypeHolderSource<?> holder) {
+            for (JavaSource<?> nested : holder.getNestedTypes()) {
+                collectAnnotated(nested, packagePrefix, dstStarImport, 
annotated, file);
+            }
+        }
+    }
+
+    /**
+     * Whether the annotation is the {@code @DstWorld} of camel-test-dst. 
Roaster cannot tell which of two or more
+     * on-demand imports provides a type and resolves it to the package of the 
file instead; an on-demand import of
+     * camel-test-dst then decides (two on-demand imports that both provide 
{@code DstWorld} do not compile, and a world
+     * package has no {@code DstWorld} type of its own).
+     */
+    private static boolean isDstWorld(AnnotationSource<?> a, String 
packagePrefix, boolean dstStarImport) {
+        return DST_WORLD.equals(a.getQualifiedName())
+                || dstStarImport && "DstWorld".equals(a.getName())
+                        && a.getQualifiedName().equals(packagePrefix + 
"DstWorld");
+    }
+
+    private static boolean isJavaFile(Path p) {
+        return p.getFileName().toString().endsWith(".java") && 
Files.isRegularFile(p);
+    }
+
+    /** Read as the rest of the plugin reads sources: a test file in another 
encoding must not fail the build. */
+    private static String read(Path file) throws IOException {
+        return new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
+    }
+
+    /** A required single value, which must not be blank. */
+    private static String value(AnnotationSource<JavaClassSource> annotation, 
String member, Path file) {
+        List<String> answer = values(annotation, member, file);
+        if (answer.size() != 1 || answer.get(0).isBlank()) {
+            throw new IllegalArgumentException("@DstWorld in " + file + " 
needs a non-empty " + member);
+        }
+        return answer.get(0);
+    }
+
+    private static List<String> values(AnnotationSource<JavaClassSource> 
annotation, String member, Path file) {
+        String literal = annotation.getLiteralValue(member);
+        if (literal == null) {
+            return new ArrayList<>();
+        }
+        try {
+            return parseStrings(literal);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException(
+                    "@DstWorld in " + file + ": " + member + " must be string 
literals (" + e.getMessage() + "): "
+                                               + literal,
+                    e);
+        }
+    }
+
+    /**
+     * The DST tests that run the world: the classes in the same package that 
have a {@code @DstTest} method and name
+     * the world class.
+     */
+    private static List<String> findTests(Path world, String name) throws 
IOException {
+        Pattern uses = Pattern.compile("\\b" + Pattern.quote(name) + "\\b");
+        List<String> answer = new ArrayList<>();
+        try (Stream<Path> list = Files.list(world.getParent())) {
+            for (Path file : 
list.filter(DstWorldsHelper::isJavaFile).toList()) {
+                String text = read(file);
+                if (!file.equals(world) && text.contains("@DstTest") && 
uses.matcher(text).find()) {
+                    String fileName = file.getFileName().toString();
+                    answer.add(fileName.substring(0, fileName.length() - 
".java".length()));
+                }
+            }
+        }
+        answer.sort(null);
+        return answer;
+    }
+
+    /**
+     * The strings of an annotation value as it is in the source: a string 
literal, string literals joined with
+     * {@code +}, or an array of those in braces.
+     *
+     * @throws IllegalArgumentException for anything else (a constant, a text 
block, a method call)
+     */
+    static List<String> parseStrings(String source) {
+        return new LiteralParser(source).parse();
+    }
+
+    private static final class LiteralParser {
+        private final String text;
+        private int pos;
+
+        LiteralParser(String text) {
+            this.text = text;
+        }
+
+        List<String> parse() {
+            List<String> answer = new ArrayList<>();
+            skipSpace();
+            if (consume('{')) {
+                skipSpace();
+                while (!consume('}')) {
+                    answer.add(concatenation());
+                    skipSpace();
+                    if (!consume(',') && !(pos < text.length() && 
text.charAt(pos) == '}')) {
+                        throw error("',' or '}' expected");
+                    }
+                    skipSpace();
+                }
+            } else {
+                answer.add(concatenation());
+            }
+            skipSpace();
+            if (pos != text.length()) {
+                throw error("unexpected text");
+            }
+            return answer;
+        }
+
+        private String concatenation() {
+            StringBuilder sb = new StringBuilder();
+            do {
+                skipSpace();
+                sb.append(literal());
+                skipSpace();
+            } while (consume('+'));
+            return sb.toString();
+        }
+
+        private String literal() {
+            if (!consume('"')) {
+                throw error("a string literal expected");
+            }
+            if (text.startsWith("\"\"", pos)) {
+                throw error("text blocks are not supported");
+            }
+            StringBuilder sb = new StringBuilder();
+            while (true) {
+                if (pos >= text.length()) {
+                    throw error("unterminated string literal");
+                }
+                char c = text.charAt(pos++);
+                if (c == '"') {
+                    return sb.toString();
+                } else if (c == '\\') {
+                    sb.append(escape());
+                } else {
+                    sb.append(c);
+                }
+            }
+        }
+
+        private char escape() {
+            if (pos >= text.length()) {
+                throw error("unterminated escape");
+            }
+            char c = text.charAt(pos++);
+            return switch (c) {
+                case 'b' -> '\b';
+                case 't' -> '\t';
+                case 'n' -> '\n';
+                case 'f' -> '\f';
+                case 'r' -> '\r';
+                case 's' -> ' ';
+                case '"', '\'', '\\' -> c;
+                case 'u' -> unicode();
+                default -> throw error("unsupported escape \\" + c);
+            };
+        }
+
+        private char unicode() {
+            while (pos < text.length() && text.charAt(pos) == 'u') {
+                pos++;
+            }
+            if (pos + 4 > text.length()) {
+                throw error("invalid unicode escape");
+            }
+            try {
+                char c = (char) Integer.parseInt(text.substring(pos, pos + 4), 
16);
+                pos += 4;
+                return c;
+            } catch (NumberFormatException e) {
+                throw error("invalid unicode escape");
+            }
+        }
+
+        private boolean consume(char c) {
+            if (pos < text.length() && text.charAt(pos) == c) {
+                pos++;
+                return true;
+            }
+            return false;
+        }
+
+        private void skipSpace() {
+            while (pos < text.length() && 
Character.isWhitespace(text.charAt(pos))) {
+                pos++;
+            }
+        }
+
+        private IllegalArgumentException error(String message) {
+            return new IllegalArgumentException(message + " at position " + 
pos);
+        }
+    }
+}
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
index 03de7850b076..e19e6fc17c86 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
@@ -67,6 +67,7 @@ import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.ComponentModel.ComponentOptionModel;
 import org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel;
 import org.apache.camel.tooling.model.ComponentModel.EndpointOptionModel;
+import org.apache.camel.tooling.model.DstWorldModel;
 import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.tooling.model.SupportLevel;
 import org.apache.camel.tooling.util.JavadocHelper;
@@ -124,6 +125,10 @@ public class EndpointSchemaGeneratorMojo extends 
AbstractGeneratorMojo {
     protected List<Path> sourceRoots;
     protected Map<String, String> sources = new HashMap<>();
     protected Map<String, JavaSource<?>> parsed = new HashMap<>();
+    protected List<Path> testSourceRoots;
+    // the fake worlds of the DST tests of the module by component scheme, and 
the schemes written (CAMEL-26438)
+    private Map<String, List<DstWorldModel>> dstWorlds;
+    private final Set<String> writtenSchemes = new HashSet<>();
 
     @Inject
     public EndpointSchemaGeneratorMojo(MavenProjectHelper projectHelper, 
BuildContext buildContext) {
@@ -192,6 +197,41 @@ public class EndpointSchemaGeneratorMojo extends 
AbstractGeneratorMojo {
 
             processSchemas(models, classElement, uriEndpoint, label, schemes, 
titles, extendsSchemes);
         }
+        checkDstWorlds();
+    }
+
+    /** The fake worlds of the DST tests of the module (in its test sources) 
for the component with this scheme. */
+    List<DstWorldModel> findDstWorlds(String scheme) throws 
MojoExecutionException {
+        writtenSchemes.add(scheme);
+        return loadDstWorlds().getOrDefault(scheme, List.of());
+    }
+
+    private Map<String, List<DstWorldModel>> loadDstWorlds() throws 
MojoExecutionException {
+        if (dstWorlds == null) {
+            if (testSourceRoots == null) {
+                testSourceRoots = 
project.getTestCompileSourceRoots().stream().map(Paths::get).toList();
+            }
+            try {
+                dstWorlds = DstWorldsHelper.findDstWorlds(testSourceRoots);
+            } catch (IOException | IllegalArgumentException e) {
+                throw new MojoExecutionException("Error reading the @DstWorld 
classes: " + e.getMessage(), e);
+            }
+        }
+        return dstWorlds;
+    }
+
+    /**
+     * A world that names a component the module does not have (or a world in 
a module without components) would be left
+     * out of the catalog without a word.
+     */
+    void checkDstWorlds() throws MojoExecutionException {
+        for (Map.Entry<String, List<DstWorldModel>> e : 
loadDstWorlds().entrySet()) {
+            if (!writtenSchemes.contains(e.getKey())) {
+                throw new MojoExecutionException(
+                        "@DstWorld of " + e.getValue().get(0).getJavaType() + 
" names the component " + e.getKey()
+                                                 + ", which is not one of the 
components of this module: " + writtenSchemes);
+            }
+        }
     }
 
     private void processSchemas(
@@ -309,6 +349,7 @@ public class EndpointSchemaGeneratorMojo extends 
AbstractGeneratorMojo {
         SchemaHelper.addModelMetadata(componentModel, project);
         SchemaHelper.addModelMetadata(componentModel, 
classElement.getAnnotation(Metadata.class));
         addBodyTypes(componentModel, classElement);
+        findDstWorlds(scheme).forEach(componentModel::addDstWorld);
 
         String json = JsonMapper.createParameterJsonSchema(componentModel);
 
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojo.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojo.java
new file mode 100644
index 000000000000..b27d811ba873
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojo.java
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+package org.apache.camel.maven.packaging;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.StringJoiner;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.DstWorldModel;
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.camel.tooling.util.PackageHelper;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProjectHelper;
+import org.codehaus.plexus.build.BuildContext;
+
+import static org.apache.camel.tooling.util.PackageHelper.findCamelDirectory;
+
+/**
+ * Generates the table of the fake worlds in the camel-test-dst documentation 
(and its copy in the catalog) from the
+ * {@code dstWorlds} sections of the component JSON files of the catalog 
(CAMEL-26438).
+ */
+@Mojo(name = "update-dst-worlds-doc", threadSafe = true, defaultPhase = 
LifecyclePhase.GENERATE_RESOURCES)
+public class UpdateDstWorldsDocMojo extends AbstractGeneratorMojo {
+
+    static final String START_TOKEN = "// dst worlds: START";
+    static final String END_TOKEN = "// dst worlds: END";
+    static final String DOC = "test-dst.adoc";
+
+    @Parameter(defaultValue = 
"${project.basedir}/src/generated/resources/org/apache/camel/catalog/components")
+    protected File componentsDir;
+
+    @Parameter(defaultValue = 
"${project.basedir}/src/generated/resources/org/apache/camel/catalog/docs")
+    protected File docsDir;
+
+    @Parameter(defaultValue = "${project.basedir}/")
+    protected File baseDir;
+
+    @Inject
+    public UpdateDstWorldsDocMojo(MavenProjectHelper projectHelper, 
BuildContext buildContext) {
+        super(projectHelper, buildContext);
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException {
+        File dstDir = findCamelDirectory(baseDir, 
"components/camel-test/camel-test-dst");
+        if (dstDir == null || !componentsDir.isDirectory()) {
+            getLog().debug("No camel-test-dst folder or no catalog components 
found, skipping execution");
+            return;
+        }
+        try {
+            String table = renderTable(findWorlds(componentsDir.toPath()));
+            Path doc = dstDir.toPath().resolve("src/main/docs/" + DOC);
+            if (updateDoc(doc, table)) {
+                getLog().info("Updated the table of DST worlds in " + doc);
+            }
+            // the catalog copy of the page was made before this goal ran
+            Path copy = docsDir.toPath().resolve(DOC);
+            if (Files.isRegularFile(copy)) {
+                updateDoc(copy, table);
+            }
+        } catch (IOException e) {
+            throw new MojoExecutionException("Error updating the table of DST 
worlds", e);
+        }
+    }
+
+    /** A world with the component it belongs to. */
+    record Row(String artifactId, DstWorldModel world) {
+    }
+
+    /** The worlds of the component JSON files in the directory, sorted by 
artifact id and world name. */
+    static List<Row> findWorlds(Path dir) throws IOException {
+        List<Row> rows = new ArrayList<>();
+        List<Path> files;
+        try (Stream<Path> list = Files.list(dir)) {
+            files = list.filter(p -> 
p.getFileName().toString().endsWith(".json")).sorted().toList();
+        }
+        for (Path file : files) {
+            String text = Files.readString(file);
+            if (!text.contains("\"dstWorlds\"")) {
+                continue;
+            }
+            ComponentModel model = JsonMapper.generateComponentModel(text);
+            for (DstWorldModel world : model.getDstWorlds()) {
+                rows.add(new Row(model.getArtifactId(), world));
+            }
+        }
+        rows.sort(Comparator.comparing(Row::artifactId).thenComparing(r -> 
r.world().getName()));
+        return rows;
+    }
+
+    /** The AsciiDoc table of the worlds. */
+    static String renderTable(List<Row> rows) {
+        StringJoiner sj = new StringJoiner("\n");
+        sj.add("[cols=\"2,3,3,4,2\"]");
+        sj.add("|===");
+        sj.add("|Component |World (tests) |Fake |What it covers |Issues");
+        for (Row row : rows) {
+            DstWorldModel w = row.world();
+            sj.add("");
+            sj.add("|" + row.artifactId());
+            String tests = w.getTests().isEmpty() ? "" : " (" + 
code(w.getTests()) + ")";
+            sj.add("|`" + w.getName() + "`" + tests);
+            sj.add("|" + escape(w.getFake()));
+            if (w.getCovers().isEmpty()) {
+                sj.add("|");
+            } else {
+                sj.add("a|* " + escape(w.getCovers().get(0)));
+                for (int i = 1; i < w.getCovers().size(); i++) {
+                    sj.add("* " + escape(w.getCovers().get(i)));
+                }
+            }
+            sj.add("|" + String.join(", ", w.getIssues()));
+        }
+        sj.add("|===");
+        return sj.toString();
+    }
+
+    private static String code(List<String> names) {
+        StringJoiner sj = new StringJoiner(", ");
+        names.forEach(n -> sj.add("`" + n + "`"));
+        return sj.toString();
+    }
+
+    /** A value on one line of its cell. */
+    private static String escape(String text) {
+        return text.replace("|", "\\|").replaceAll("\\s*[\\r\\n]+\\s*", " ");
+    }
+
+    /** Puts the table between the markers of the page; returns whether the 
page changed. */
+    static boolean updateDoc(Path doc, String table) throws IOException, 
MojoExecutionException {
+        String text = PackageHelper.loadText(doc);
+        String changed = replaceTable(text, table);
+        if (changed == null) {
+            throw new MojoExecutionException("No " + START_TOKEN + " / " + 
END_TOKEN + " markers in " + doc);
+        }
+        if (changed.equals(text)) {
+            return false;
+        }
+        PackageHelper.writeText(doc.toFile(), changed);
+        return true;
+    }
+
+    /** The text with the table between the markers, or null if the text has 
no markers. */
+    static String replaceTable(String text, String table) {
+        int start = text.indexOf(START_TOKEN);
+        int end = text.indexOf(END_TOKEN);
+        if (start < 0 || end < start) {
+            return null;
+        }
+        return text.substring(0, start) + START_TOKEN + "\n" + table + "\n" + 
text.substring(end);
+    }
+}
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/DstWorldsHelperTest.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/DstWorldsHelperTest.java
new file mode 100644
index 000000000000..a6b37104c494
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/DstWorldsHelperTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+package org.apache.camel.maven.packaging;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.tooling.model.DstWorldModel;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test of {@link DstWorldsHelper} and of the check of the worlds in 
{@link EndpointSchemaGeneratorMojo}
+ * (CAMEL-26438).
+ */
+class DstWorldsHelperTest {
+
+    private static final String WORLD = """
+            package org.foo.dst;
+
+            import org.apache.camel.test.dst.Dst;
+            import org.apache.camel.test.dst.DstWorld;
+
+            /** The world. */
+            @DstWorld(component = "foo",
+                      fake = "FakeFoo: a queue, " + "with \\"quotes\\" and a 
\\u00e9",
+                      covers = { "polling", "the extender" + " of locks", },
+                      faults = "receive fails",
+                      issues = { "CAMEL-1", "CAMEL-2" })
+            final class FooWorld {
+                FooWorld(Dst dst) {
+                }
+            }
+            """;
+
+    private static final String IMPORTS
+            = "import org.apache.camel.test.dst.Dst;\nimport 
org.apache.camel.test.dst.DstWorld;\n";
+
+    private static final String TEST = """
+            package org.foo.dst;
+
+            class %s {
+                @DstTest(seeds = 10)
+                void test(Dst dst) {
+                    new FooWorld(dst).run();
+                }
+            }
+            """;
+
+    @TempDir
+    Path dir;
+
+    @Test
+    void testParseStrings() {
+        assertEquals(List.of("a"), DstWorldsHelper.parseStrings("\"a\""));
+        assertEquals(List.of("ab c"), DstWorldsHelper.parseStrings("\"a\" + 
\"b\"\n  + \" c\""));
+        assertEquals(List.of(), DstWorldsHelper.parseStrings("{}"));
+        assertEquals(List.of("a", "bc"), DstWorldsHelper.parseStrings("{ 
\"a\", \"b\" + \"c\", }"));
+        assertEquals(List.of("say \"hi\"\t\\ \u00e9"),
+                DstWorldsHelper.parseStrings("\"say \\\"hi\\\"\\t\\\\ 
\\u00e9\""));
+        assertEquals(List.of("a, b", "{c}"), 
DstWorldsHelper.parseStrings("{\"a, b\",\"{c}\"}"));
+    }
+
+    @Test
+    void testParseStringsRejectsWhatIsNotALiteral() {
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("FAKE"));
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("\"a\" + FAKE"));
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("{ \"a\" \"b\" }"));
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("\"\"\"\n text\n \"\"\""));
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("\"open"));
+        assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.parseStrings("\"a\".trim()"));
+    }
+
+    @Test
+    void testFindWorldAndItsTests() throws IOException {
+        write("org/foo/dst/FooWorld.java", WORLD);
+        write("org/foo/dst/FooDstTest.java", TEST.formatted("FooDstTest"));
+        write("org/foo/dst/FooOtherDstTest.java", 
TEST.formatted("FooOtherDstTest"));
+        // not a DST test, and a DST test of another world
+        write("org/foo/dst/FooHelper.java", "package org.foo.dst;\nclass 
FooHelper { FooWorld world; }\n");
+        write("org/foo/dst/BarDstTest.java", 
TEST.formatted("BarDstTest").replace("FooWorld", "BarWorld"));
+        // a class that only mentions the annotation, and one with another 
annotation of that name
+        write("org/foo/Notes.java", "package org.foo;\n/** See DstWorld. 
*/\nclass Notes {\n}\n");
+        write("org/foo/Other.java", "package org.foo;\nimport 
org.other.DstWorld;\n@DstWorld(component = \"foo\")\n"
+                                    + "class Other {\n}\n");
+
+        Map<String, List<DstWorldModel>> worlds = 
DstWorldsHelper.findDstWorlds(List.of(dir, dir.resolve("missing")));
+        assertEquals(List.of("foo"), List.copyOf(worlds.keySet()));
+        assertEquals(1, worlds.get("foo").size());
+        DstWorldModel world = worlds.get("foo").get(0);
+        assertEquals("FooWorld", world.getName());
+        assertEquals("org.foo.dst.FooWorld", world.getJavaType());
+        assertEquals("FakeFoo: a queue, with \"quotes\" and a \u00e9", 
world.getFake());
+        assertEquals(List.of("polling", "the extender of locks"), 
world.getCovers());
+        assertEquals(List.of("receive fails"), world.getFaults());
+        assertEquals(List.of(), world.getInvariants());
+        assertEquals(List.of("CAMEL-1", "CAMEL-2"), world.getIssues());
+        assertEquals(List.of("FooDstTest", "FooOtherDstTest"), 
world.getTests());
+    }
+
+    @Test
+    void testWorldsAreSortedByName() throws IOException {
+        write("org/foo/dst/FooWorld.java", WORLD);
+        write("org/foo/dst2/AaaWorld.java", WORLD.replace("org.foo.dst;", 
"org.foo.dst2;").replace("FooWorld", "AaaWorld"));
+        List<DstWorldModel> worlds = 
DstWorldsHelper.findDstWorlds(List.of(dir)).get("foo");
+        assertEquals(List.of("AaaWorld", "FooWorld"), 
worlds.stream().map(DstWorldModel::getName).toList());
+    }
+
+    @Test
+    void testInvalidWorlds() throws IOException {
+        write("org/foo/dst/FooWorld.java", WORLD.replace("faults = \"receive 
fails\"", "faults = FAULTS"));
+        IllegalArgumentException e
+                = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("faults must be string literals"), 
e.getMessage());
+
+        write("org/foo/dst/FooWorld.java", WORLD.replace("component = 
\"foo\"", "component = \" \""));
+        e = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("needs a non-empty component"), 
e.getMessage());
+
+        write("org/foo/dst/FooWorld.java", WORLD);
+        write("org/foo/dst2/FooWorld.java", WORLD.replace("org.foo.dst;", 
"org.foo.dst2;"));
+        e = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("the same simple name"), 
e.getMessage());
+    }
+
+    @Test
+    void testWorldNotOnATopLevelClassFails() throws IOException {
+        // a world on a record, an interface, an enum or a nested class would 
be left out of the catalog
+        String head = WORLD.substring(0, WORLD.indexOf("final class 
FooWorld"));
+        for (String text : List.of(head + "record FooWorld(String a) {\n}\n", 
head + "interface FooWorld {\n}\n",
+                head + "enum FooWorld {\n    A\n}\n",
+                "package org.foo.dst;\nimport 
org.apache.camel.test.dst.DstWorld;\nclass Outer {\n"
+                                                      + 
head.substring(head.indexOf("/**"))
+                                                      + "static class FooWorld 
{\n}\n}\n")) {
+            write("org/foo/dst/FooWorld.java", text);
+            IllegalArgumentException e
+                    = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+            assertTrue(e.getMessage().contains("must be on the top-level 
class"), e.getMessage());
+        }
+        write("org/foo/dst/FooWorld.java", "import 
org.apache.camel.test.dst.DstWorld;\n@DstWorld(component = \"foo\")\n");
+        IllegalArgumentException e
+                = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("FooWorld.java"), e.getMessage());
+    }
+
+    @Test
+    void testWorldWithTwoOnDemandImports() throws IOException {
+        // Roaster resolves DstWorld to the package of the file when two or 
more on-demand imports could provide it
+        for (String star : List.of("import java.util.*;\nimport 
org.apache.camel.test.dst.*;\n",
+                "import org.apache.camel.test.dst.*;\nimport java.util.*;\n",
+                "import static java.util.Objects.*;\nimport 
org.apache.camel.test.dst.*;\n")) {
+            write("org/foo/dst/FooWorld.java", WORLD.replace(IMPORTS, star));
+            assertEquals(List.of("org.foo.dst.FooWorld"),
+                    
DstWorldsHelper.findDstWorlds(List.of(dir)).getOrDefault("foo", 
List.of()).stream()
+                            .map(DstWorldModel::getJavaType).toList(),
+                    star);
+        }
+    }
+
+    @Test
+    void testDstWorldOfAnotherPackageIsNotAWorld() throws IOException {
+        // control: the file does not name camel-test-dst, so its DstWorld is 
another annotation (of org.other or of its
+        // package); the text check leaves it out before it is parsed
+        write("org/foo/dst/FooWorld.java", WORLD.replace(IMPORTS, "import 
java.util.*;\nimport org.other.*;\n"));
+        assertEquals(Map.of(), DstWorldsHelper.findDstWorlds(List.of(dir)));
+
+        // an import by name wins over the on-demand import of camel-test-dst
+        write("org/foo/dst/FooWorld.java", WORLD.replace(IMPORTS,
+                "import java.util.*;\nimport 
org.apache.camel.test.dst.*;\nimport org.other.DstWorld;\n"));
+        assertEquals(Map.of(), DstWorldsHelper.findDstWorlds(List.of(dir)));
+    }
+
+    @Test
+    void testDstWorldNotResolvedFails() throws IOException {
+        // the file names camel-test-dst only in a comment and does not import 
the annotation: never left out silently
+        write("org/foo/dst/FooWorld.java", WORLD.replace(IMPORTS,
+                "import java.util.*;\nimport org.other.*;\n// a world for 
org.apache.camel.test.dst.DstWorld\n"));
+        IllegalArgumentException e
+                = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("@DstWorld on FooWorld in ")
+                && e.getMessage().contains("is not resolved to " + 
DstWorldsHelper.DST_WORLD), e.getMessage());
+    }
+
+    @Test
+    void testWorldAfterAHelperClassOfTheFile() throws IOException {
+        // the world is the class named after the file, also when a 
package-private class comes first
+        String head = WORLD.substring(0, WORLD.indexOf("/** The world. */"));
+        String world = WORLD.substring(WORLD.indexOf("/** The world. 
*/")).replace("final class", "public final class");
+        write("org/foo/dst/FooWorld.java", head + "class FooHelper {\n}\n\n" + 
world);
+        assertEquals(List.of("org.foo.dst.FooWorld"),
+                
DstWorldsHelper.findDstWorlds(List.of(dir)).getOrDefault("foo", 
List.of()).stream()
+                        .map(DstWorldModel::getJavaType).toList());
+
+        // a world on another class of the file fails
+        write("org/foo/dst/FooWorld.java", head + "public class FooWorld 
{\n}\n\n"
+                                           + world.replace("public final class 
FooWorld", "final class BarWorld")
+                                                   .replace("FooWorld(Dst 
dst)", "BarWorld(Dst dst)"));
+        IllegalArgumentException e
+                = assertThrows(IllegalArgumentException.class, () -> 
DstWorldsHelper.findDstWorlds(List.of(dir)));
+        assertTrue(e.getMessage().contains("must be on the top-level class 
named after the file (FooWorld)"),
+                e.getMessage());
+    }
+
+    @Test
+    void testOtherTestSourcesDoNotFailTheBuild() throws IOException {
+        // a test file in another encoding, a directory named like a source, 
the same root twice, and sources that only
+        // mention the annotation
+        Path latin1 = dir.resolve("org/foo/Latin1Test.java");
+        Files.createDirectories(latin1.getParent());
+        Files.write(latin1,
+                "package org.foo;\n// caf\u00e9 DstWorld\nclass Latin1Test 
{\n}\n".getBytes(StandardCharsets.ISO_8859_1));
+        Files.createDirectories(dir.resolve("org/foo/Odd.java"));
+        // sources that only mention the annotation of camel-test-dst, in a 
comment or a string
+        write("org/foo/dst/Mention1Test.java", "package org.foo.dst;\nimport 
org.apache.camel.test.dst.*;\n"
+                                               + "/** A world has {@code 
@DstWorld}. */\nclass Mention1Test {\n}\n");
+        write("org/foo/dst/Mention2Test.java", "package org.foo.dst;\n// see 
@org.apache.camel.test.dst.DstWorld\n"
+                                               + "class Mention2Test {\n}\n");
+        write("org/apache/camel/test/dst/Mention3Test.java", "package 
org.apache.camel.test.dst;\n"
+                                                             + "/** Not a 
@DstWorld. */\nclass Mention3Test {\n}\n");
+        write("org/foo/dst/Mention4Test.java", "package org.foo.dst;\nclass 
Mention4Test {\n    String s = \""
+                                               + "import 
org.apache.camel.test.dst.DstWorld; @DstWorld(component = x)\";\n}\n");
+        write("org/foo/dst/FooWorld.java", WORLD);
+        assertEquals(List.of("FooWorld"),
+                DstWorldsHelper.findDstWorlds(List.of(dir, 
dir.resolve("org/..")))
+                        
.get("foo").stream().map(DstWorldModel::getName).toList());
+    }
+
+    @Test
+    void testMojoAddsTheWorldsOfItsSchemeAndFailsOnAnUnknownScheme() throws 
Exception {
+        write("org/foo/dst/FooWorld.java", WORLD);
+        EndpointSchemaGeneratorMojo mojo = new EndpointSchemaGeneratorMojo();
+        mojo.project = new MavenProject();
+        mojo.testSourceRoots = List.of(dir);
+        assertEquals(List.of("FooWorld"), 
mojo.findDstWorlds("foo").stream().map(DstWorldModel::getName).toList());
+        assertEquals(List.of(), mojo.findDstWorlds("foos"));
+        mojo.checkDstWorlds();
+
+        EndpointSchemaGeneratorMojo other = new EndpointSchemaGeneratorMojo();
+        other.project = new MavenProject();
+        other.testSourceRoots = List.of(dir);
+        assertEquals(List.of(), other.findDstWorlds("bar"));
+        MojoExecutionException e = assertThrows(MojoExecutionException.class, 
other::checkDstWorlds);
+        assertTrue(e.getMessage().contains("org.foo.dst.FooWorld names the 
component foo"), e.getMessage());
+
+        // a module without components
+        EndpointSchemaGeneratorMojo none = new EndpointSchemaGeneratorMojo();
+        none.project = new MavenProject();
+        none.testSourceRoots = List.of(dir);
+        assertThrows(MojoExecutionException.class, none::checkDstWorlds);
+    }
+
+    private Path write(String name, String text) throws IOException {
+        Path file = dir.resolve(name);
+        Files.createDirectories(file.getParent());
+        Files.writeString(file, text);
+        return file;
+    }
+}
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojoTest.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojoTest.java
new file mode 100644
index 000000000000..86d8d4163e46
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/UpdateDstWorldsDocMojoTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+package org.apache.camel.maven.packaging;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.DstWorldModel;
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test of {@link UpdateDstWorldsDocMojo} (CAMEL-26438).
+ */
+class UpdateDstWorldsDocMojoTest {
+
+    @TempDir
+    Path dir;
+
+    @Test
+    void testTableFromTheCatalog() throws Exception {
+        writeComponent("zoo", "camel-zoo", world("ZooWorld", 
List.of("ZooDstTest"), List.of("polling | paging\n  and more")));
+        writeComponent("foo", "camel-foo", world("FooWorld", 
List.of("FooADstTest", "FooBDstTest"), List.of("a", "b")),
+                world("BarWorld", List.of(), List.of()));
+        writeComponent("plain", "camel-plain");
+
+        List<UpdateDstWorldsDocMojo.Row> rows = 
UpdateDstWorldsDocMojo.findWorlds(dir);
+        assertEquals(List.of("BarWorld", "FooWorld", "ZooWorld"), 
rows.stream().map(r -> r.world().getName()).toList());
+
+        assertEquals("""
+                [cols="2,3,3,4,2"]
+                |===
+                |Component |World (tests) |Fake |What it covers |Issues
+
+                |camel-foo
+                |`BarWorld`
+                |the fake of BarWorld
+                |
+                |CAMEL-1
+
+                |camel-foo
+                |`FooWorld` (`FooADstTest`, `FooBDstTest`)
+                |the fake of FooWorld
+                a|* a
+                * b
+                |CAMEL-1
+
+                |camel-zoo
+                |`ZooWorld` (`ZooDstTest`)
+                |the fake of ZooWorld
+                a|* polling \\| paging and more
+                |CAMEL-1
+                |===""", UpdateDstWorldsDocMojo.renderTable(rows));
+    }
+
+    @Test
+    void testUpdateDoc() throws Exception {
+        Path doc = dir.resolve("test-dst.adoc");
+        Files.writeString(doc, "intro\n\n// dst worlds: START\nold table\n// 
dst worlds: END\n\nafter\n");
+        assertTrue(UpdateDstWorldsDocMojo.updateDoc(doc, "|===\n|new\n|==="));
+        assertEquals("intro\n\n// dst worlds: START\n|===\n|new\n|===\n// dst 
worlds: END\n\nafter\n", Files.readString(doc));
+        assertFalse(UpdateDstWorldsDocMojo.updateDoc(doc, "|===\n|new\n|==="));
+
+        assertNull(UpdateDstWorldsDocMojo.replaceTable("no markers", "|==="));
+        Files.writeString(doc, "no markers\n");
+        assertThrows(MojoExecutionException.class, () -> 
UpdateDstWorldsDocMojo.updateDoc(doc, "|==="));
+    }
+
+    private static DstWorldModel world(String name, List<String> tests, 
List<String> covers) {
+        DstWorldModel world = new DstWorldModel();
+        world.setName(name);
+        world.setJavaType("org.foo.dst." + name);
+        world.setFake("the fake of " + name);
+        world.setCovers(covers);
+        world.setIssues(List.of("CAMEL-1"));
+        world.setTests(tests);
+        return world;
+    }
+
+    private void writeComponent(String scheme, String artifactId, 
DstWorldModel... worlds) throws Exception {
+        ComponentModel model = new ComponentModel();
+        model.setName(scheme);
+        model.setScheme(scheme);
+        model.setArtifactId(artifactId);
+        for (DstWorldModel world : worlds) {
+            model.addDstWorld(world);
+        }
+        Files.writeString(dir.resolve(scheme + ".json"), 
JsonMapper.createParameterJsonSchema(model));
+    }
+}

Reply via email to