{"id":13467,"date":"2021-03-29T16:34:31","date_gmt":"2021-03-29T23:34:31","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cse\/?p=13467"},"modified":"2021-03-29T19:39:07","modified_gmt":"2021-03-30T02:39:07","slug":"unit-testing-azure-devops-ui-extensions","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/unit-testing-azure-devops-ui-extensions\/","title":{"rendered":"Unit Testing Azure DevOps UI Extensions"},"content":{"rendered":"<h2>Background<\/h2>\n<p>One of our automotive customers uses Azure DevOps for embedded software development. Process wise they need to comply with the A-SPICE standard (<a href=\"http:\/\/www.automotivespice.com\/\">Automotive SPICE\u00ae<\/a>) which regulates the software creation process and allows assessments and traceability for error root cause research in case of, for example, a car accident. Basically, it is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/V-Model\">V-Model process<\/a> where all upper and lower levels are linked with each other.<\/p>\n<blockquote><p>An example: a <em>legal requirement<\/em> is specified and linked to a <em>technical requirement<\/em> which specifies how to fulfill the legal constraints. The <em>technical requirement<\/em> could specify valid test result ranges of a computation. Since this all needs to be recorded, the computation (e.g. a program code file) needs to be linked to the <em>technical requirement<\/em> too. If the result of that program complies to the <em>technical requirements<\/em> the link is called valid or consistent.<\/p><\/blockquote>\n<p>Creating the links and checking the validity is a labor-intensive manual task. It is also not easy to track manually if a validation must be redone because the program code was changed. Therefore, the overall goal is to automate or at least assist manual validation as much as possible.<\/p>\n<p>Azure Boards provides the possibility to highly customize software development processes and already includes linking features between any type of so-called Work Items (in this case a legal or technical requirement, epic, user stories etc.). Therefore, it was chosen as the foundation for the implementation.<\/p>\n<p>In a first phase we helped develop the customization and logic to invalidate links to code artifacts if they were changed in a git-based code repository. This helps to identify which validations must be redone.<\/p>\n<p>Errors in the automation tools could break the traceability aspect of A-SPICE and hence ultimately could result in legal consequences for our customer (e.g., when a required root cause examination could not be answered in court). Therefore we needed to achieve complete unit test code coverage for all parts of the automation tools. This will help identifying functional errors in early development, especially as these tools grow in complexity in the next phases.<\/p>\n<p>Part of these automation tools is an Azure Boards Work Item UI extension based on the <a href=\"https:\/\/developer.microsoft.com\/en-us\/azure-devops\/\">Typescript React Framework for Azure DevOps<\/a>. In this post we will only focus on this Work Item UI extension and how to achieve unit testing with complete code coverage for it.<\/p>\n<p>&nbsp;<\/p>\n<h2>Requirements and Challenges<\/h2>\n<p>This post will demonstrate how we achieved the non-functional requirement for the development and what we did to achieve the functional requirement.<\/p>\n<h4>Functional Requirements:<\/h4>\n<ul>\n<li style=\"text-align: left;\">Assist in finding linked code artifacts (stored in git repository) which need to be re-validated (VersionedItems)<\/li>\n<li>Any requirement can have multiple owners<\/li>\n<\/ul>\n<h4>Non-Functional Requirement:<\/h4>\n<ul>\n<li>Achieve complete code coverage for all components of the developed automation tools<\/li>\n<\/ul>\n<h4>Challenges to achieve the Functional Requirement:<\/h4>\n<ul>\n<li>Azure Boards does not support git repository Versioned-Item links, therefore we had to develop a custom UI control<\/li>\n<li>Azure Boards does not support multiple owners of a Work Item, therefore we had to develop a custom UI control<\/li>\n<\/ul>\n<p>Both controls are packed together into one Azure DevOps Extension.<\/p>\n<h4>Challenges to achieve the Non-Functional Requirements:<\/h4>\n<ul>\n<li>Creating unit tests for Azure DevOps React\/Typescript based UI components with complete code coverage<\/li>\n<\/ul>\n<p>To unit test the components we need to cut off all external service dependencies. If an SDK method is used which leads to an API call there is a need to short cut the request and instead return a local dummy value for testing. To achieve the complete code coverage for the UI components there were following six sub-challenges to resolve these external service dependencies:<\/p>\n<ul>\n<li>Mocking return values of <a href=\"https:\/\/github.com\/Microsoft\/azure-devops-extension-sdk\">Azure DevOps Extension SDK<\/a>\u00a0methods\n<ul>\n<li>Azure DevOps environment related input parameters like current User, input parameter for the controls. How can we mock these parameters?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>Mocking <a href=\"https:\/\/github.com\/Microsoft\/azure-devops-extension-sdk\">Azure DevOps Extension SDK<\/a> service clients\n<ul>\n<li>The SDK provides accessors to service clients. How can we mock these clients and their method results?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>Triggering Azure DevOps Extension <em>WorkItemFormUI <\/em>event callbacks\n<ul>\n<li>The controls implement callbacks (event handlers) to be informed about changes to the WorkItem (e.g. onSave, onDelete, onRefresh). These are triggered when the user is interacting with the <em>WorkItemFormUI<\/em>. How can we trigger them in the unit test?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>Mocking return values and service clients of the <a href=\"https:\/\/github.com\/Microsoft\/azure-devops-extension-api\">Azure DevOps Extension API<\/a>\n<ul>\n<li>A service client for accessing the Git repos is used, how can we mock this client, and its methods return values?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>Mock the external REST API calls and spy on the values transmitted\n<ul>\n<li>Additional services were developed to achieve the functional requirement. How can we mock the generic REST Client we are using?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>Mocking and spy on logging methods (Application Insights SDK)\n<ul>\n<li>How can we verify that logs are correctly processed by the logging system?<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>Solution<\/h2>\n<h3>Functional Requirements<\/h3>\n<p>We will not go into implementation details for the functional requirements in this blog post. The example Azure DevOps Extension (<a href=\"https:\/\/github.com\/h2floh\/azure-dev-ops-react-ui-unit-testing\/\">provided on GitHub<\/a>) contains two Work Item UI controls:<\/p>\n<ol>\n<li><a href=\"https:\/\/github.com\/h2floh\/azure-dev-ops-react-ui-unit-testing\/tree\/main\/src\/VersionedItemsTable\">Versioned Items Table<\/a> \u2014 can link versioned items (code artifacts) from a distinct Azure Git repo branch to the work item. To do so it will save the state via a REST API external to Azure DevOps Services.<\/li>\n<li><a href=\"https:\/\/github.com\/h2floh\/azure-dev-ops-react-ui-unit-testing\/tree\/main\/src\/MultiIdentityPicker\">Multi Identity Picker\u00a0<\/a>\u2014 selects multiple identities from the Azure DevOps Organization associate AAD Tenant and saves the information into a text field of the work item.\n<p><figure id=\"attachment_13469\" aria-labelledby=\"figcaption_attachment_13469\" class=\"wp-caption aligncenter\" ><a href=\"https:\/\/github.com\/h2floh\/azure-dev-ops-react-ui-unit-testing\/\"><img decoding=\"async\" class=\"wp-image-13469\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2021\/03\/image-for-post.png\" alt=\"Conceptual overview of the UI controls and their service interactions\" width=\"1280\" height=\"720\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2021\/03\/image-for-post.png 1280w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2021\/03\/image-for-post-300x169.png 300w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2021\/03\/image-for-post-1024x576.png 1024w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2021\/03\/image-for-post-768x432.png 768w\" sizes=\"(max-width: 1280px) 100vw, 1280px\" \/><\/a><figcaption id=\"figcaption_attachment_13469\" class=\"wp-caption-text\">Conceptual overview of the UI controls and their service interactions<\/figcaption><\/figure><\/li>\n<\/ol>\n<p>The main technical difference between both components is that one will save the state externally and the other internally to Azure DevOps Services.<\/p>\n<p>Both will use the TypeScript based Azure DevOps Extension SDK and API to communicate with Azure DevOps Service and do logging towards an Azure based Application Insights.<\/p>\n<p>&nbsp;<\/p>\n<h3>Non-Functional Requirement<\/h3>\n<p>This is the main technical content we focus on in this post. Step by step we show the code to test, the mocks needed and how the associate unit test looks like.<\/p>\n<h5>Mocking return values of Azure DevOps Extension SDK methods<\/h5>\n<p><strong><em>The code:<\/em> <\/strong>The Azure DevOps SDK must be initialized, after that we retrieve configuration values from it. We need to be able to specify the return values for the test (here: <em>FieldName<\/em>).<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/67e78b23f700322f8c5b709011f5af42.js\"><\/script><\/p>\n<p><strong><em>The mock:<\/em><\/strong> Due to the reactive style the <strong>SDK.init()\u00a0<\/strong>function needs to return a resolved promise in order to process the lambda function in the <strong>.then()<\/strong> branch of the code. If we mock <strong>SDK.init()<\/strong> the configuration values would not be read at all. The\u00a0<strong>getConfiguration()<\/strong> function can be easily mocked by returning a JSON object with the exact structure and data we expect. We added all configuration values for both UI components at once. <b>If you additionally need to modify specific values<\/b> within a test, it is possible to create an additional function mock which will be accessible in the unit test to overwrite the return value. In this example we do that for the <em>RepositoryId parameter <\/em>with a default value of\u00a0<em>\u2018gitrepo\u2019<\/em>, by introducing a mocked function\u00a0<strong>mockRepositoryId()<\/strong>. However, we are never changing the default value in the example tests. The information of the current user will be mocked with fixed values.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/c74701c0699d46258d5889e5775e01e2.js\"><\/script><\/p>\n<p><strong><em>The test:<\/em><\/strong> The control will call <strong>componentDidMount()<\/strong> within the rendering process and will retrieve the parameter values set in the mock file. We don&#8217;t provide any assigned user data in this test. The default behavior is to use the current user as a value what is checked here.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/bdf5370243b9669c799f16b9acb6fe14.js\"><\/script><\/p>\n<h5>Mocking <a href=\"https:\/\/github.com\/Microsoft\/azure-devops-extension-sdk\">Azure DevOps Extension SDK<\/a> services<\/h5>\n<p><strong><em>The code:<\/em><\/strong>\u00a0We retrieve the\u00a0<em>Work Item Form Service<\/em>\u00a0with the function\u00a0<strong>getService().\u00a0<\/strong>With this service we can access and modify the current work item opened in the UI. We want to retrieve the value of the field specified to hold the state of this component. This is done by the method\u00a0<strong>getFieldValue().<\/strong><\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/ee4122af63304468502e222b6df7ed3a.js\"><\/script><\/p>\n<p><strong><em>The mock:<\/em><\/strong> We need two different services returned from <strong>getService()<\/strong>\u00a0depending on the input parameters. In case the W<em>orkItemFormService\u00a0<\/em>is requested we just return the\u00a0<strong>getFieldValue()<\/strong>\u00a0method as a mocked function\u00a0<strong>mockGetFieldValue()<\/strong>. This allows us to inject the return value at the unit test level. In this case we do not distinguish what field is requested. If you are accessing several fields you would need to create an implementation for <strong>mockGetFieldValue()\u00a0<\/strong>which will react to different input parameters.<\/p>\n<p>As an example of how to react to input parameters you can have a look at the mocked\u00a0<strong>searchIdentitiesAsync()<\/strong>\u00a0method of the\u00a0<em>IdentityService<\/em>. Depending on the query value we return another identity.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/ed79d6a1851199bc27acf77017427464.js\"><\/script><\/p>\n<p><strong><em>The test:<\/em><\/strong> Defines the return value for <strong>getFieldValue()\u00a0<\/strong>and expects that the name associated to the email address will be displayed by the control. The mapping between email and display name happens in the already mocked\u00a0<strong>searchIdentitesAsync()<\/strong>\u00a0method.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/eae3c133fc944f5f6906bfa7df2ff2fe.js\"><\/script><\/p>\n<h5>Trigger Azure DevOps Extension\u00a0<em>WorkItemFormUI <\/em>events<\/h5>\n<p><strong><em>The code:<\/em><\/strong> When you develop a Work Item UI Control you can hook into events from the Work Item UI like saving, changed work item state etc. The control will react on the\u00a0<strong>onSaved()\u00a0<\/strong>event and register it\u2019s callback to the SDK.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/bc89a3f6d331e506f7bd0dd1cdb3a9d3.js\"><\/script><\/p>\n<p><em><strong>The mock:<\/strong>\u00a0<\/em>For this mock we had to figure out the exact type of the return value expected by the SDKs <strong>register()\u00a0<\/strong>function. As you can see in the code snippet the return value will be assigned to the <strong>spyWorkItemCallBack<\/strong>\u00a0function pointer from within the mocked\u00a0<strong>register()<\/strong>\u00a0function.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/4a08e60a245d60bf6b1e7ddde59e6257.js\"><\/script><\/p>\n<p><strong><em>The test:<\/em>\u00a0<\/strong>Now we can trigger the callback function registered by the control within our tests by calling it via the <strong>spyWorkItemCallBack\u00a0<\/strong>function pointer.<\/p>\n<p>This will work in the same way for all other 5 event hooks possible to subscribe on.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/75e4f2de5bfe09514154d067ec821945.js\"><\/script><\/p>\n<h5>Mocking return values and classes of the\u00a0Azure DevOps Extension API<\/h5>\n<p><strong><em>The code: <\/em><\/strong>One of the controls is loading all items of an Azure Git repo by utilizing the Azure DevOps Extension APIs Git Client. The control first has to load the Git Client with <strong>getClient()<\/strong> and only uses the <strong>getItems()<\/strong> method of this client.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/5987388d68b68fc7d54b502bba30982f.js\"><\/script><\/p>\n<p><strong><em>The mock:<\/em> <\/strong>The mock for the Git Client only contains the <strong>getItems()\u00a0<\/strong>method and will return the value of the\u00a0<strong>mockGetItems\u00a0<\/strong>function if the\u00a0<em>RepositoryId<\/em> is known, otherwise it will throw an error.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/9a7369f976eb4d8494c1f2622618ab29.js\"><\/script><\/p>\n<p>In order to retrieve the mocked Git Client the <strong>getClient()\u00a0<\/strong>function from the Extension API has to be mocked as well. Otherwise, a call to <strong>getClient()<\/strong> will try to return an object of the original un-mocked class. If you use an additional API you will have to include it in this list.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/ffd9115ba0066a98d75d7db9f2c10838.js\"><\/script><\/p>\n<p><em><strong>The test:<\/strong><\/em> In the unit test it is now possible to define the return value of the Git Client <strong>getItems()<\/strong> method.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/a4893b0b4da702cb27bb092fb49cc01e.js\"><\/script><\/p>\n<h5>Mock the external Rest API calls and spy on the values transmitted<\/h5>\n<p><strong><em>The code:<\/em><\/strong> In order to access external APIs we inherited the <strong>RestClientBase\u00a0<\/strong>class of the Extensions API. We are mainly leveraging the\u00a0<strong>_issueRequest()\u00a0<\/strong>method provided by it and it&#8217;s data structures for issuing HTTP requests.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/120ab8d8090393ab853a3e0378fb4289.js\"><\/script><\/p>\n<p><strong><em>The mock:<\/em><\/strong> We mocked the base class in favor of our own client to really test our REST client&#8217;s code.<\/p>\n<p>The mocked base class can now throw an error on demand, returning values we set at test level for specific API calls (evaluated by method and endpointURL) and even log the request values such as body and headers for a test validation in case of a POST call.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/2294f107fd1043485eea5e61a1464ee9.js\"><\/script><\/p>\n<p><strong><em>The test:<\/em><\/strong> We now can verify in a test a successful POST and if the data is transmitted correctly.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/5e31f019012883314fa92a336b298e42.js\"><\/script><\/p>\n<h5>Mocking and spy on logging methods (Application Insights SDK)<\/h5>\n<p><strong><em>The code:<\/em><\/strong> We log the exceptions and other information with a logger implementation of Application Insights. Here we log any exception occurring while communicating with the Git Rest Client.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/1dadb19e17acee6d960f6a84a4572779.js\"><\/script><\/p>\n<p><strong><em>The mock:<\/em><\/strong> We mock the method <strong>trackException()<\/strong>\u00a0from the\u00a0<strong>ApplicationInsights<\/strong>\u00a0package and pass the exception as a parameter to the\u00a0<strong>mockTrackException\u00a0<\/strong>function.<\/p>\n<p>In this way we can verify the logged exception content and can have complete code coverage for our logger implementation too.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/6942f684539cc27aaf3baf1cdbda9b78.js\"><\/script><\/p>\n<p><strong><em>The test:<\/em> <\/strong>We can trigger an exception for the mocked Git\u00a0<strong>getItems()\u00a0<\/strong>method and validate that the exception was logged and the logged content is correct.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/h2floh\/6b672b9a74351b750bb8e9baad762612.js\"><\/script><\/p>\n<h2>Summary<\/h2>\n<p>It is possible to mock all relevant parts of the Azure DevOps Extension SDK, Extension API as well as other external APIs to successfully create meaningful unit tests without external dependencies and reach full code coverage. Therefore, we could comply to the non-functional requirements and build out the foundation to identify errors in the A-SPICE automation tools logic early stage.<\/p>\n<p>Additional resources:<\/p>\n<ul>\n<li><a href=\"http:\/\/www.automotivespice.com\/\">Automotive SPICE\u00ae<\/a><\/li>\n<li><a href=\"https:\/\/www.lhpes.com\/blog\/what-is-aspice-in-automotive\">What is ASPICE in Automotive?<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/h2floh\/azure-dev-ops-react-ui-unit-testing\">The example code, mocks and the unit tests on GitHub<\/a><\/li>\n<li><a title=\"https:\/\/github.com\/microsoft\/azure-devops-extension-sample\" href=\"https:\/\/github.com\/microsoft\/azure-devops-extension-sample\">Sample web extension for Azure DevOps (github.com)<\/a>\u200b\u200b\u200b\u200b\u200b\u200b\u200b<\/li>\n<li><a href=\"https:\/\/developer.microsoft.com\/en-us\/azure-devops\/\">Azure DevOps React UI &#8211; Formula Design System (microsoft.com)<\/a><\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>The Team<\/h2>\n<p>Below is the Microsoft team that worked with me on this project, making sure we meet the customer requirements while coming up with a solution that works broadly all along the way!<\/p>\n<p><a href=\"https:\/\/ch.linkedin.com\/in\/chris-lomonico-20b1835\">Chris Lomonico \u2013 Microsoft | LinkedIn<\/a>,\u00a0<a href=\"https:\/\/www.linkedin.com\/in\/dariuszparys\">Dariusz Parys \u2013 Microsoft | LinkedIn<\/a>, <a href=\"https:\/\/de.linkedin.com\/in\/kaizimmerm\">Kai Zimmermann \u2013 Microsoft | LinkedIn<\/a>,\u00a0<a href=\"https:\/\/www.linkedin.com\/in\/scheeroliver\/mit-dem-microsoft-band-sdk-entwickeln\/\">Oliver Scheer \u2013 Microsoft | LinkedIn<\/a>, <a href=\"https:\/\/fr.linkedin.com\/in\/tomconte\">Thomas Cont\u00e9 \u2013 Microsoft | LinkedIn<\/a>,\u00a0<a href=\"https:\/\/www.linkedin.com\/in\/ericabarone\">Erica Barone \u2013 Microsoft | LinkedIn<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mocking cloud service dependencies in modern applications is essential for independent unit testing. We show how to mock all relevant parts of the Azure DevOps Extension SDK, Azure DevOps Extension API as well as other external APIs to create meaningful unit tests for Azure DevOps UI Extensions and reach full code coverage.<\/p>\n","protected":false},"author":54990,"featured_media":13526,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,16],"tags":[3303,3301,3302,3300],"class_list":["post-13467","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","category-devops","tag-azure-devops-extension","tag-react","tag-typescript","tag-unittest"],"acf":[],"blog_post_summary":"<p>Mocking cloud service dependencies in modern applications is essential for independent unit testing. We show how to mock all relevant parts of the Azure DevOps Extension SDK, Azure DevOps Extension API as well as other external APIs to create meaningful unit tests for Azure DevOps UI Extensions and reach full code coverage.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/13467","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/54990"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=13467"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/13467\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/13526"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=13467"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=13467"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=13467"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}