Back to Repositories

Testing Template Rendering and Environment Processing in Insomnia

This comprehensive test suite evaluates the rendering functionality in the Insomnia API client, focusing on environment variable interpolation and template processing. The tests verify template rendering, context building, and environment variable cascading across different scopes.

Test Coverage Overview

The test suite provides extensive coverage of the rendering system’s core capabilities:
  • Basic variable interpolation and template processing
  • Nested object and array rendering
  • Environment variable cascading and inheritance
  • Complex template expressions and recursive rendering
  • Error handling and edge cases

Implementation Analysis

The tests utilize Jest’s testing framework to validate rendering functionality through multiple approaches:
  • Unit tests for individual render functions
  • Integration tests for environment variable context building
  • Validation of template processing with nested scopes
  • Error case handling and boundary testing

Technical Details

Key technical components include:
  • Vitest for test execution
  • Fluent builder pattern for test data construction
  • Environment and request group model schemas
  • Custom rendering utilities and template processors

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Isolated test cases with proper setup/teardown
  • Comprehensive edge case coverage
  • Clear test organization and grouping
  • Detailed assertion messages
  • Proper error handling validation

kong/insomnia

packages/insomnia/src/common/__tests__/render.test.ts

            
import { createBuilder } from '@develohpanda/fluent-builder';
import { beforeEach, describe, expect, it } from 'vitest';

import * as models from '../../models';
import { environmentModelSchema, requestGroupModelSchema } from '../../models/__schemas__/model-schemas';
import { Environment } from '../../models/environment';
import { Workspace } from '../../models/workspace';
import * as renderUtils from '../render';

const envBuilder = createBuilder(environmentModelSchema);
const reqGroupBuilder = createBuilder(requestGroupModelSchema);

describe('render tests', () => {
  beforeEach(async () => {
    await models.project.all();
    await models.settings.getOrCreate();
    envBuilder.reset();
    reqGroupBuilder.reset();
  });

  describe('render()', () => {
    it('renders hello world', async () => {
      const rendered = await renderUtils.render('Hello {{ msg }}!', {
        msg: 'World',
      });
      expect(rendered).toBe('Hello World!');
    });

    it('renders custom tag: uuid', async () => {
      const rendered = await renderUtils.render('Hello {% uuid %}!');
      expect(rendered).toMatch(/Hello [a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}!/);
    });

    it('renders nested object', async () => {
      const rendered = await renderUtils.render('Hello {{ users[0].name }}!', {
        users: [
          {
            name: 'FooBar',
          },
        ],
      });
      expect(rendered).toBe('Hello FooBar!');
    });

    it('returns invalid template', async () => {
      const rendered = await renderUtils.render('Hello {{ msg }!', {
        msg: 'World',
      });
      expect(rendered).toBe('Hello {{ msg }!');
    });

    it('handles variables using tag before tag is defined as expected (incorrect order)', async () => {
      const rootEnvironment = envBuilder
        .data({
          consume: '{{ replaced }}',
          hashed: "{% hash 'md5', 'hex', value %}",
          replaced: "{{ hashed | replace('f67565de946a899a534fd908e7eef872', 'cat') }}",
          value: 'ThisIsATopSecretValue',
        })
        .dataPropertyOrder({
          '&': ['value', 'replaced', 'hashed', 'consume'],
        })
        .build();

      const context = await renderUtils.buildRenderContext({ ancestors: [], rootEnvironment });
      expect(context).toEqual({
        value: 'ThisIsATopSecretValue',
        hashed: 'f67565de946a899a534fd908e7eef872',
        replaced: 'f67565de946a899a534fd908e7eef872',
        consume: 'f67565de946a899a534fd908e7eef872',
      });
      // In runtime, this context is used to render, which re-evaluates the expression for replaced in the rootEnvironment by using the built context
      // Regression test from issue 1917 - https://github.com/Kong/insomnia/issues/1917
      const renderExpression = await renderUtils.render(rootEnvironment.data.replaced, context);
      expect(renderExpression).toBe('cat');
    });
  });

  describe('buildRenderContext()', () => {
    it('cascades properly', async () => {
      const ancestors = [
        reqGroupBuilder.environment({ foo: 'parent', ancestor: true }).build(),
        reqGroupBuilder.environment({ foo: 'grandparent', ancestor: true }).build(),
      ];

      const rootEnvironment = envBuilder.data({ foo: 'root', root: true }).build();
      const subEnvironment = envBuilder.data({ foo: 'sub', sub: true }).build();

      const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment, subEnvironment });

      expect(context).toEqual({
        foo: 'parent',
        ancestor: true,
        root: true,
        sub: true,
      });
    });

    it('rendered recursive should not infinite loop', async () => {
      const ancestors = [
        reqGroupBuilder.environment({ recursive: '{{ recursive }}/hello' }).build(),
      ];

      const context = await renderUtils.buildRenderContext({ ancestors });
      // This is longer than 3 because it multiplies every time (1 -> 2 -> 4 -> 8)
      expect(context).toEqual({
        recursive: '{{ recursive }}/hello/hello/hello/hello/hello/hello/hello/hello',
      });
    });

    it('does not recursive render if itself is not used in var', async () => {
      const root = envBuilder.data({
        proto: 'http',
        domain: 'base.com',
        url: '{{ proto }}://{{ domain }}',
      }).build();

      const sub = envBuilder.data({
        proto: 'https',
        domain: 'sub.com',
        port: 8000,
        url: '{{ proto }}://{{ domain }}:{{ port }}',
      }).build();

      const ancestors = [
        reqGroupBuilder.environment({
          proto: 'https',
          domain: 'folder.com',
          port: 7000,
        }).build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment: root, subEnvironment: sub });
      expect(context).toEqual({
        proto: 'https',
        domain: 'folder.com',
        port: 7000,
        url: 'https://folder.com:7000',
      });
    });

    it('does the thing', async () => {
      const root = envBuilder.data({ url: 'insomnia.rest' }).build();
      const sub = envBuilder.data({ url: '{{ url }}/sub' }).build();
      const ancestors = [
        reqGroupBuilder.environment({
          url: '{{ url }}/{{ name }}',
          name: 'folder',
        }).build(),
      ];

      const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment: root, subEnvironment: sub });
      expect(context).toEqual({
        url: 'insomnia.rest/sub/folder',
        name: 'folder',
      });
    });

    it('render up to 3 recursion levels', async () => {
      const ancestors = [
        reqGroupBuilder.environment({
          d: '/d',
          c: '/c{{ d }}',
          b: '/b{{ c }}',
          a: '/a{{ b }}',
          test: 'http://insomnia.rest{{ a }}',
        }).build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        d: '/d',
        c: '/c/d',
        b: '/b/c/d',
        a: '/a/b/c/d',
        test: 'http://insomnia.rest/a/b/c/d',
      });
    });

    it('rendered sibling environment variables', async () => {
      const ancestors = [
        reqGroupBuilder.environment({
          sibling: 'sibling',
          test: '{{ sibling }}/hello',
        }).build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        sibling: 'sibling',
        test: 'sibling/hello',
      });
    });

    it('rendered parent environment variables', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({
            test: '{{ grandparent }} parent',
          })
          .build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            grandparent: 'grandparent',
          })
          .build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        grandparent: 'grandparent',
        test: 'grandparent parent',
      });
    });

    it('rendered parent same name environment variables', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({
            base_url: '{{ base_url }}/resource',
          })
          .build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            base_url: 'https://insomnia.rest',
          })
          .build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        base_url: 'https://insomnia.rest/resource',
      });
    });

    it('rendered parent, ignoring sibling environment variables', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({
            host: 'parent.com',
          })
          .build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            host: 'grandparent.com',
            node: {
              admin: 'admin',
              test: 'test',
              port: 8080,
            },
            urls: {
              admin: 'https://{{ host }}/{{ node.admin }}',
              test: 'https://{{ host }}/{{ node.test }}',
            },
          })
          .build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(await renderUtils.render('{{ urls.admin }}/foo', context)).toBe(
        'https://parent.com/admin/foo',
      );
      expect(await renderUtils.render('{{ urls.test }}/foo', context)).toBe(
        'https://parent.com/test/foo',
      );
    });

    it('renders child environment variables', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({
            parent: 'parent',
          })
          .build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            test: '{{ parent }} grandparent',
          })
          .build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        parent: 'parent',
        test: 'parent grandparent',
      });
    });

    it('works with object arrays', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({})
          .build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            users: [
              {
                name: 'Foo',
              },
              {
                name: 'Bar',
              },
            ],
          })
          .build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        users: [
          {
            name: 'Foo',
          },
          {
            name: 'Bar',
          },
        ],
      });
    });

    it('works with ordered objects', async () => {
      const obj = {
        users: [
          {
            name: 'Foo',
            id: 1,
          },
          {
            name: 'Bar',
            id: 2,
          },
        ],
      };
      const order = {
        '&': ['users'],
        '&~|users~|0': ['id', 'name'],
        '&~|users~|1': ['id', 'name'],
      };
      const requestGroup = reqGroupBuilder.name('Parent').environment(obj).environmentPropertyOrder(order).build();
      const rootEnvironment = envBuilder.name('Parent').data(obj).dataPropertyOrder(order).build();
      const subEnvironment = envBuilder.name('Sub').data(obj).dataPropertyOrder(order).build();

      const groupCtx = await renderUtils.buildRenderContext({ ancestors: [requestGroup] });
      const rootCtx = await renderUtils.buildRenderContext({ ancestors: [], rootEnvironment });
      const subCtx = await renderUtils.buildRenderContext({ ancestors: [], subEnvironment });

      const expected = {
        users: [
          {
            id: 1,
            name: 'Foo',
          },
          {
            id: 2,
            name: 'Bar',
          },
        ],
      };
      expect(groupCtx).toEqual(expected);
      expect(rootCtx).toEqual(expected);
      expect(subCtx).toEqual(expected);
    });

    it('merges nested properties when rendering', async () => {
      const ancestors = [
        reqGroupBuilder
          .name('Parent')
          .environment({
            parent: 'parent',
            nested: {
              common: 'parent',
              parentA: 'pa',
              parentB: 'pb',
            },
          }).build(),
        reqGroupBuilder
          .name('Grandparent')
          .environment({
            test: '{{ parent }} grandparent',
            nested: {
              common: 'grandparent',
              grandParentA: 'gpa',
              grandParentB: 'gpb',
            },
          }).build(),
      ];
      const context = await renderUtils.buildRenderContext({ ancestors });
      expect(context).toEqual({
        parent: 'parent',
        test: 'parent grandparent',
        nested: {
          common: 'parent',
          grandParentA: 'gpa',
          grandParentB: 'gpb',
          parentA: 'pa',
          parentB: 'pb',
        },
      });
    });

    it('cascades properly and renders', async () => {
      const ancestors = [
        reqGroupBuilder
          .environment({
            url: '{{ base_url }}/resource',
            ancestor: true,
            winner: 'folder parent',
          })
          .build(),
        reqGroupBuilder
          .environment({
            ancestor: true,
            winner: 'folder grandparent',
          })
          .build(),
      ];
      const subEnvironment = envBuilder.data({
        winner: 'sub',
        sub: true,
        base_url: 'https://insomnia.rest',
      }).build();
      const rootEnvironment = envBuilder.data({
        winner: 'root',
        root: true,
        base_url: 'ignore this',
      }).build();
      const context = await renderUtils.buildRenderContext(
        { ancestors, rootEnvironment, subEnvironment },
      );
      expect(context).toEqual({
        base_url: 'https://insomnia.rest',
        url: 'https://insomnia.rest/resource',
        ancestor: true,
        winner: 'folder parent',
        root: true,
        sub: true,
      });
    });

    it('handles variables using tag after tag is defined as expected (correct order)', async () => {
      const rootEnvironment = envBuilder
        .data({
          consume: '{{ replaced }}',
          hashed: "{% hash 'md5', 'hex', value %}",
          replaced: "{{ hashed | replace('f67565de946a899a534fd908e7eef872', 'cat') }}",
          value: 'ThisIsATopSecretValue',
        })
        .dataPropertyOrder({
          '&': ['value', 'hashed', 'replaced', 'consume'],
        })
        .build();
      const context = await renderUtils.buildRenderContext({ ancestors: [], rootEnvironment });
      expect(context).toEqual({
        value: 'ThisIsATopSecretValue',
        hashed: 'f67565de946a899a534fd908e7eef872',
        replaced: 'cat',
        consume: 'cat',
      });
    });

    it('handles variables being used in tags', async () => {
      const rootEnvironment = envBuilder
        .data({
          hash_input: '{{ orderId }}{{ secret }}',
          hash_input_expected: '123456789012345ThisIsATopSecretValue',
          orderId: 123456789012345,
          password: "{% hash 'sha512', 'hex', hash_input %}",
          password_expected: "{% hash 'sha512', 'hex', hash_input_expected %}",
          secret: 'ThisIsATopSecretValue',
        })
        .build();
      const context = await renderUtils.buildRenderContext({ ancestors: [], rootEnvironment });
      expect(context).toEqual({
        hash_input: '123456789012345ThisIsATopSecretValue',
        hash_input_expected: '123456789012345ThisIsATopSecretValue',
        orderId: 123456789012345,
        password:
        'ea84d15f33d3f9e9098fe01659b1ea0599d345770bba20ba98bf9056676a83ffe6b5528b2451ad04badbf690cf3009a94c510121cc6897045f8bb4ba0826134c',
        password_expected:
        'ea84d15f33d3f9e9098fe01659b1ea0599d345770bba20ba98bf9056676a83ffe6b5528b2451ad04badbf690cf3009a94c510121cc6897045f8bb4ba0826134c',
        secret: 'ThisIsATopSecretValue',
      });
    });

    it('works with minimal parameters', async () => {
      const context = await renderUtils.buildRenderContext({ });
      expect(context).toEqual({});
    });
  });

  describe('render()', () => {
    it('correctly renders simple Object', async () => {
      const newObj = await renderUtils.render(
        {
          foo: '{{ foo }}',
          bar: 'bar',
          baz: '{{ bad }}',
        },
        {
          foo: 'bar',
          bad: 'hi',
        },
      );
      expect(newObj).toEqual({
        foo: 'bar',
        bar: 'bar',
        baz: 'hi',
      });
    });

    it('correctly renders complex Object', async () => {
      const d = new Date();
      const obj = {
        foo: '{{ foo }}',
        null: null,
        bool: true,
        date: d,
        undef: undefined,
        num: 1234,
        nested: {
          foo: '{{ foo }}',
          arr: [1, 2, '{{ foo }}'],
        },
      };
      const newObj = await renderUtils.render(obj, {
        foo: 'bar',
      });
      expect(newObj).toEqual({
        foo: 'bar',
        null: null,
        bool: true,
        date: d,
        undef: undefined,
        num: 1234,
        nested: {
          foo: 'bar',
          arr: [1, 2, 'bar'],
        },
      });
      // Make sure original request isn't changed
      expect(obj.foo).toBe('{{ foo }}');
      expect(obj.nested.foo).toBe('{{ foo }}');
      expect(obj.nested.arr[2]).toBe('{{ foo }}');
    });

    it('fails on bad template', async () => {
      try {
        await renderUtils.render(
          {
            foo: '{{ foo }',
            bar: 'bar',
            baz: '{{ bad }}',
          },
          {
            foo: 'bar',
          },
        );
        fail('Render should have failed');
      } catch (err) {
        expect(err.message).toContain('Failed to render environment variables');
      }
    });

    it('keep on error setting', async () => {
      const template = '{{ foo }} {% invalid "hi" %}';
      const context = {
        foo: 'bar',
      };
      const resultOnlyVars = await renderUtils.render(
        template,
        context,
        null,
        renderUtils.KEEP_ON_ERROR,
      );
      expect(resultOnlyVars).toBe('{{ foo }} {% invalid "hi" %}');

      try {
        await renderUtils.render(template, context, null);
        fail('Render should not have succeeded');
      } catch (err) {
        expect(err.message).toBe('unknown block tag: invalid');
      }
    });

    it('outputs correct error path', async () => {
      const template = {
        foo: [
          {
            bar: '{% foo %}',
          },
        ],
      };

      try {
        await renderUtils.render(template);
        fail('Should have failed to render');
      } catch (err) {
        expect(err.path).toBe('foo[0].bar');
      }
    });

    it('outputs correct error path when private first node', async () => {
      const template = {
        _foo: {
          _bar: {
            baz: '{% foo %}',
          },
        },
      };

      try {
        await renderUtils.render(template);
        fail('Should have failed to render');
      } catch (err) {
        expect(err.path).toBe('_bar.baz');
      }
    });
  });

  describe('getRenderedGrpcRequestMessage()', () => {
    it('renders only the body for a grpc request ', async () => {
      const w1 = await models.workspace.create();
      const env = await models.environment.create({
        parentId: w1._id,
        data: {
          foo: 'bar',
          host: 'testb.in:9000',
        },
      });
      const grpcRequest = await models.grpcRequest.create({
        parentId: w1._id,
        name: 'hi {{ foo }}',
        url: '{{ host }}',
        description: 'hi {{ foo }}',
        body: {
          text: '{ "prop": "{{ foo }}" }',
        },
      });
      const request = await renderUtils.getRenderedGrpcRequestMessage({ request: grpcRequest, environmentId: env._id });
      expect(request).toEqual(
        expect.objectContaining({
          text: '{ "prop": "bar" }',
        }),
      );
    });
  });

  describe('getRenderedGrpcRequest()', () => {
    let w1: Workspace;
    let env: Environment;

    beforeEach(async () => {
      w1 = await models.workspace.create();
      env = await models.environment.create({
        parentId: w1._id,
        data: {
          foo: 'bar',
          host: 'testb.in:9000',
        },
      });
    });

    it('renders all grpc request properties', async () => {
      const grpcRequest = await models.grpcRequest.create({
        parentId: w1._id,
        name: 'hi {{ foo }}',
        url: '{{ host }}',
        description: 'hi {{ foo }}',
        body: {
          text: '{ "prop": "{{ foo }}" }',
        },
      });
      const request = await renderUtils.getRenderedGrpcRequest({ request: grpcRequest, environmentId: env._id });
      expect(request).toEqual(
        expect.objectContaining({
          name: 'hi bar',
          url: 'testb.in:9000',
          description: 'hi bar',
          body: {
            text: '{ "prop": "bar" }',
          },
        }),
      );
    });

    it('renders but ignores the body for a grpc request ', async () => {
      const grpcRequest = await models.grpcRequest.create({
        parentId: w1._id,
        name: 'hi {{ foo }}',
        url: '{{ host }}',
        description: 'hi {{ foo }}',
        body: {
          text: '{ "prop": "{{ foo }}" }',
        },
      });
      const request = await renderUtils.getRenderedGrpcRequest({ request: grpcRequest, environmentId: env._id, skipBody: true });
      expect(request).toEqual(
        expect.objectContaining({
          name: 'hi bar',
          url: 'testb.in:9000',
          description: 'hi bar',
          body: {
            text: '{ "prop": "{{ foo }}" }',
          },
        }),
      );
    });

    it('should still render with bad description', async () => {
      const grpcRequest = await models.grpcRequest.create({
        parentId: w1._id,
        name: 'hi {{ foo }}',
        url: '{{ host }}',
        description: 'hi {{ some error }}',
        body: {
          text: '{ "prop": "{{ foo }}" }',
        },
      });
      const request = await renderUtils.getRenderedGrpcRequest({ request: grpcRequest, environmentId: env._id });
      expect(request).toEqual(
        expect.objectContaining({
          name: 'hi bar',
          url: 'testb.in:9000',
          description: 'hi {{ some error }}',
          body: {
            text: '{ "prop": "bar" }',
          },
        }),
      );
    });
  });
});