{
    "componentChunkName": "component---src-templates-blog-post-tsx",
    "path": "/blog/2022-12-05-ingestion-testing-in-backstage-part-2/",
    "result": {"data":{"blogPost":{"title":"Ingestion tests in Backstage Part 2: Safely refactoring an LDAP integration","slug":"/blog/2022-12-05-ingestion-testing-in-backstage-part-2/","authorNodes":[{"name":"Charles Lowell","slug":"/people/charles-lowell/"}],"markdown":{"html":"<blockquote>\n<p>Heads up: This is the second part in a series on how to achieve real confidence in your Backstage Ingestion via testing. We’ll be relying on the techniques introduced in the first blog post to test a non-trivial external application. What follows may not make much sense without a grasp of those techniques, so I highly recommend starting with <a href=\"../2022-03-24-testing-backstage-catalog-ingestors\">Part 1</a> first and then coming back.</p>\n</blockquote>\n<p>In our article on testing your Backstage catalog ingestion, we discussed the technique of using the concepts of structured concurrency and eventual consistency to test a backstage server solely via its external interfaces: as an operating system process and an http server. The payoff for organizing your tests that way is that they end up at the ideal sweet spot intersection: replicating how the server actually runs and behaves in production while still maintaining desirable test properties such as speed, isolation, and repeatability.</p>\n<p>Rather than reaching into the guts of your ingestion code and testing the inputs and outputs of individual viscera, this approach instead embeds your server as a single holistic unit directly into your test cases. This means that not only can you test anything in your server using the exact same test harness, but also you are free to re-organize the internals of the server as you see fit, and your test cases need never change.</p>\n<p>In this article, we’ll demonstrate why this is so important by refactoring in order to upgrade a Backstage server from a legacy LDAP org processor to a new LDAP entity provider – all without changing a single line of the test case.</p>\n<p>If you recall, the fundamental strategy of the basic ingestion test case for locations defined in a fixed YAML was as straightforward as we dared to make it:</p>\n<ol>\n<li>Start a Backstage server;</li>\n<li>Let it run for a while; and</li>\n<li>Check to make sure that the entities we expect were ingested.</li>\n</ol>\n<p>We’ll use the exact same strategy as before with the only difference being that instead of checking that service components are ingested from our YAML files, we’ll check that our user entities are properly ingested from our LDAP server.</p>\n<p><figure class=\"figure\"><img src=\"/img/2022-ingestion-testing-in-backstage-part-2/test-architecture.svg\" title=\"Backstage Client talks to Backstage Server, which talks to LDAP Simulator\"><figcaption class=\"figure-caption\">Testing Architecture featuring a simulated LDAP server</figcaption></figure></p>\n<p>Using a simulator as a test double means that we get all the benefits of isolation and repeatability when using a mock or stub, but without having to sacrifice any of the confidence in the viability of our test because it’s actually using 100% of the production code with no additives or substitutes.</p>\n<p>In fact, we’ve developed an LDAP simulator just for this purpose, which we can use inside of our test case. With this tool in hand, we can start up a simulated LDAP server instantaneously – right before we start our Backstage server – so the only thing that is different is that our test goes from this:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">yield</span> <span class=\"token function\">createBackstage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>to this:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">yield</span> <span class=\"token function\">createLDAPServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">yield</span> <span class=\"token function\">createBackstage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>That was easy! Now that we have our very own LDAP server embedded in our test case, all we have to do is seed it with user and group data, and then use our convergent assertions to confirm that those records were ingested in the catalog:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">let</span> catalog<span class=\"token operator\">:</span> CatalogApi<span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">beforeEach</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token operator\">*</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">yield</span> <span class=\"token function\">createLDAPServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    users<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n      <span class=\"token punctuation\">{</span>\n        cn<span class=\"token operator\">:</span> <span class=\"token string\">'cowboyd'</span><span class=\"token punctuation\">,</span>\n        uid<span class=\"token operator\">:</span> <span class=\"token string\">'cowboyd'</span><span class=\"token punctuation\">,</span>\n        name<span class=\"token operator\">:</span> <span class=\"token string\">'Charles Lowell'</span><span class=\"token punctuation\">,</span>\n        uuid<span class=\"token operator\">:</span> <span class=\"token string\">'xyz123-cowboyd'</span><span class=\"token punctuation\">,</span>\n        mail<span class=\"token operator\">:</span> <span class=\"token string\">'cowboyd@example.com'</span><span class=\"token punctuation\">,</span>\n        password<span class=\"token operator\">:</span> <span class=\"token string\">'password'</span><span class=\"token punctuation\">,</span>\n        avatar<span class=\"token operator\">:</span> <span class=\"token string\">'https://avatars.dicebear.com/api/open-peeps/cowboyd.svg'</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  catalog <span class=\"token operator\">=</span> <span class=\"token keyword\">yield</span> <span class=\"token function\">createBackstage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">/// other catalog tests</span>\n\nit<span class=\"token punctuation\">.</span><span class=\"token function\">eventually</span><span class=\"token punctuation\">(</span><span class=\"token string\">'ingests users from LDAP into the catalog'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span><span class=\"token operator\">*</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> user <span class=\"token operator\">=</span> <span class=\"token keyword\">yield</span> catalog<span class=\"token punctuation\">.</span><span class=\"token function\">getEntityByRef</span><span class=\"token punctuation\">(</span><span class=\"token string\">'user:cowboyd'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toMatchObject</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    spec<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      profile<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        email<span class=\"token operator\">:</span> <span class=\"token string\">'cowboyd@example.com'</span><span class=\"token punctuation\">,</span>\n        picture<span class=\"token operator\">:</span> <span class=\"token string\">'https://avatars.dicebear.com/api/open-peeps/cowboyd.svg'</span><span class=\"token punctuation\">,</span>\n        displayName<span class=\"token operator\">:</span> <span class=\"token string\">'Charles Lowell'</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"upgrade-with-confidence\" style=\"position:relative;\"><a href=\"#upgrade-with-confidence\" aria-label=\"upgrade with confidence permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Upgrade with confidence</h3>\n<p>One of the main points I’ve been stressing in these articles is that this style of test only ever uses the public API of Backstage, which means that no matter what is going on under the covers, our test case verifies the most important aspect of its behavior: what it will do when you actually use it. And because our test only uses the “outside” of Backstage, it means that we have a free hand to change whatever is inside without worrying that we might break our tests.</p>\n<p>In the example repository, our initial LDAP integration used a processor combined with a custom user transformer in order to map the fields from our LDAP user entry into a Backstage user entity. Here is our transformer that maps the name and avatar fields from our entry into the spec.profile.displayName and spec.profile.picture fields of our user entity:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">async</span> <span class=\"token function\">userTransformer</span><span class=\"token punctuation\">(</span>vendor<span class=\"token punctuation\">,</span> config<span class=\"token punctuation\">,</span> entry<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> user <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">defaultUserTransformer</span><span class=\"token punctuation\">(</span>vendor<span class=\"token punctuation\">,</span> config<span class=\"token punctuation\">,</span> entry<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">merge</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    spec<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      profile<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        displayName<span class=\"token operator\">:</span> vendor<span class=\"token punctuation\">.</span><span class=\"token function\">decodeStringAttribute</span><span class=\"token punctuation\">(</span>entry<span class=\"token punctuation\">,</span> <span class=\"token string\">'name'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n        picture<span class=\"token operator\">:</span> vendor<span class=\"token punctuation\">.</span><span class=\"token function\">decodeStringAttribute</span><span class=\"token punctuation\">(</span>entry<span class=\"token punctuation\">,</span> <span class=\"token string\">'avatar'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>But this is the <a href=\"https://backstage.io/docs/integrations/ldap/org#using-a-processor-instead-of-a-provider\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">old way</a>. The new way is to use an entity provider, and a simple set of field mappings in your app-config.yaml. We want the transformation to look like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">users</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">map</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">displayName</span><span class=\"token punctuation\">:</span> name\n    <span class=\"token key atrule\">picture</span><span class=\"token punctuation\">:</span> avatar</code></pre></div>\n<p>Let’s start with a passing test suite:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">PASS  src/catalog.test.ts (18.847 s)\n  catalog ingestion\n    ✓ can connect to the catalog (5348 ms)\n    ✓ ingests the artist lookup component  (4469 ms)\n    ✓ ingests the example user from ldap into the catalog (4298 ms)\n\nTest Suites: 1 passed, 1 total\nTests:       3 passed, 3 total\nSnapshots:   0 total\nTime:        19.208 s\nRan all test suites.</code></pre></div>\n<p>If we first migrate our catalog to use the entity provider instead of the processor, but don’t add the field mappings yet, our test suite automatically fails:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">FAIL  src/catalog.test.ts (21.039 s)\n  catalog ingestion\n    ✓ can connect to the catalog (4318 ms)\n    ✓ ingests the artist lookup component  (4700 ms)\n    ✕ ingests the example user from ldap into the catalog (9063 ms)\n\n  ● catalog ingestion › ingests the example user from ldap into the catalog\n\n    expect(received).toMatchObject(expected)\n\n    - Expected  - 2\n    + Received  + 1\n\n      Object {\n        \"spec\": Object {\n          \"profile\": Object {\n    -       \"displayName\": \"Charles Lowell\",\n    +       \"displayName\": \"cowboyd\",\n            \"email\": \"cowboyd@example.com\",\n    -       \"picture\": \"https://avatars.dicebear.com/api/open-peeps/cowboyd.svg\",\n          },\n        },\n      }\n\n      45 |   it.eventually('ingests the example user from ldap into the catalog', function*() {\n      46 |     let user = yield catalog.getEntityByRef('user:cowboyd')\n    > 47 |     expect(user).toMatchObject({\n         |                  ^\n      48 |       spec: {\n      49 |         profile: {\n      50 |           email: 'cowboyd@example.com',\n\n      at Object.&lt;anonymous> (catalog.test.ts:47:18)\n      at produce (../../../node_modules/@effection/core/src/future.ts:115:15)\n      at Promise.race.then.produce.state (../../../node_modules/@effection/core/src/controller/promise-controller.ts:11:9)\n          at runMicrotasks (&lt;anonymous>)\n\nTest Suites: 1 failed, 1 total\nTests:       1 failed, 2 passed, 3 total\nSnapshots:   0 total\nTime:        21.514 s\nRan all test suites.</code></pre></div>\n<p>Not only has it detected that something is amiss, but it has also told us exactly what went wrong. The record has been found, but the spec.profile.displayName and spec.profile.picture fields are no longer being populated correctly. However, if we add those mappings to our app-config.yaml the tests re-run and pass again:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">PASS  src/catalog.test.ts (17.195 s)\n  catalog ingestion\n    ✓ can connect to the catalog (4962 ms)\n    ✓ ingests the artist lookup component  (5102 ms)\n    ✓ ingests the example user from ldap into the catalog (4223 ms)\n\nTest Suites: 1 passed, 1 total\nTests:       3 passed, 3 total\nSnapshots:   0 total\nTime:        17.645 s, estimated 22 s\nRan all test suites.</code></pre></div>\n<p>We completely changed the mechanism for ingestion of users from LDAP, and yet our tests did not change by a single line. Don’t believe me? <a href=\"https://github.com/cowboyd/backstage-integration-testing-example/compare/ldap...ldap-entity-provider\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Check the diff!</a></p>\n<h3 id=\"focus-on-what-matters\" style=\"position:relative;\"><a href=\"#focus-on-what-matters\" aria-label=\"focus on what matters permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Focus on what matters</h3>\n<p>We know tests are important, but they can be so difficult to set up and such a nightmare to evolve along with your codebase that most of the time we don’t even bother. But a workable and efficient solution is to imagine the simplest, most durable test you can, and then put the work in to attack the complexity – separating that from being able to express that simple, durable test in code. In the case of ingestion, the simplest thing that could possibly work is to turn it all on and see what happens, and take a “wait-and-see” attitude toward complexity.</p>","frontmatter":{"date":"December 05, 2022","description":"To test whether the Backstage is setting up the Catalog properly, you must start a Backstage server, wait for a while, and assert. This feat is easier said than done. Here is part 2 in which Charles will guide you through it in this article.","tags":["backstage","testing"],"img":{"childImageSharp":{"fixed":{"src":"/static/55b0aa4df66c229d21b92013d072543c/31987/2022-04-26-ingestion-test-in-backstage-part-2.png"}}}}}}},"pageContext":{"id":"c40a2c93-4843-5368-b9b4-ba9693f7ce6c"}},
    "staticQueryHashes": ["1241260443"]}