# Chatbot content feed (see .ddev/plans/CHATBOT-MCP-01-TYPO3-CONTENT-FEED.md) # # Pick the "local" environment in PhpStorm and run the whole file top to bottom: the manifest # request stores uids and a content URL that the later requests use. # # Needs, locally: # - http-client.private.env.json with {"local": {"cmsApiKey": ""}} (gitignored) # - the same key in TYPO3, e.g. # ddev exec vendor/bin/typo3cms configuration:set EXTENSIONS/ep_products/cmsApiKey # plus contentFeedSites etc. - or open and save the ep_products extension configuration once # in the backend, which writes all the defaults. # # The kill switch (contentFeedEnabled = 0) cannot be toggled from here; with it off, every keyed # request below answers 503. ### Manifest without a key is refused GET {{host}}/api/content/manifest > {% client.test("403 without key", function () { client.assert(response.status === 403, "expected 403, got " + response.status); }); %} ### Manifest GET {{host}}/api/content/manifest X-Api-Key: {{cmsApiKey}} > {% client.test("manifest is complete and well-formed", function () { client.assert(response.status === 200, "expected 200, got " + response.status); client.assert(response.body.items.length > 0, "manifest is empty"); client.assert(response.body.sites.length > 0, "no sites"); client.assert( response.body.items.every(function (item) { return item.type && item.uid && item.modified; }), "an item lacks type, uid or modified" ); }); var uidsOf = function (type, limit) { return response.body.items .filter(function (item) { return item.type === type; }) .slice(0, limit) .map(function (item) { return item.uid; }) .join(","); }; client.global.set("productUids", uidsOf("product", 5)); client.global.set("hotelUids", uidsOf("hotel", 5)); client.global.set("regionUids", uidsOf("region", 5)); client.global.set("newsUids", uidsOf("news", 3)); var page = response.body.items.filter(function (item) { return item.type === "page" && item.contentUrl; })[0]; client.global.set("contentUrl", page.contentUrl); var tooMany = []; for (var i = 1; i <= 101; i++) { tooMany.push(i); } client.global.set("tooManyUids", tooMany.join(",")); %} ### Products (with FAQs) GET {{host}}/api/content/records?type=product&uids={{productUids}} X-Api-Key: {{cmsApiKey}} > {% client.test("every requested product comes back, links resolved", function () { client.assert(response.status === 200, "expected 200, got " + response.status); client.assert(response.body.items.length === client.global.get("productUids").split(",").length, "products missing"); client.assert(JSON.stringify(response.body).indexOf("t3://") === -1, "unresolved t3:// link"); }); %} ### Hotels (codes include matchcode aliases) GET {{host}}/api/content/records?type=hotel&uids={{hotelUids}} X-Api-Key: {{cmsApiKey}} > {% client.test("hotels carry their codes", function () { client.assert(response.status === 200, "expected 200, got " + response.status); client.assert(response.body.items.every(function (hotel) { return Array.isArray(hotel.codes); }), "hotel without codes"); }); %} ### Regions GET {{host}}/api/content/records?type=region&uids={{regionUids}} X-Api-Key: {{cmsApiKey}} ### Blog articles GET {{host}}/api/content/records?type=news&uids={{newsUids}} X-Api-Key: {{cmsApiKey}} ### Unknown type is a 400 GET {{host}}/api/content/records?type=page&uids=1 X-Api-Key: {{cmsApiKey}} > {% client.test("400 for unknown type", function () { client.assert(response.status === 400, "expected 400, got " + response.status); }); %} ### More than 100 uids is a 400 GET {{host}}/api/content/records?type=product&uids={{tooManyUids}} X-Api-Key: {{cmsApiKey}} > {% client.test("400 above 100 uids", function () { client.assert(response.status === 400, "expected 400, got " + response.status); }); %} ### Content-only render of the first page in the manifest GET {{contentUrl}} X-Api-Key: {{cmsApiKey}} > {% client.test("bare article, never cached by proxies", function () { client.assert(response.status === 200, "expected 200, got " + response.status); client.assert(response.body.indexOf("
") === 0, "not a bare
fragment"); client.assert(response.headers.valueOf("Cache-Control") === "private, no-store", "unexpected Cache-Control"); }); %} ### Content-only render without a key is a 404 (the type must not be discoverable) GET {{contentUrl}} > {% client.test("404 without key", function () { client.assert(response.status === 404, "expected 404, got " + response.status); }); %} ### Content-only render with a wrong key is a 404 GET {{contentUrl}} X-Api-Key: definitely-wrong > {% client.test("404 with wrong key", function () { client.assert(response.status === 404, "expected 404, got " + response.status); }); %}