<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/integration/</id>
  <link href="https://www.endpointdev.com/blog/tags/integration/"/>
  <link href="https://www.endpointdev.com/blog/tags/integration/" rel="self"/>
  <updated>2025-03-28T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Testing Ansible Automation with Molecule</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/03/testing-ansible-with-molecule/"/>
      <id>https://www.endpointdev.com/blog/2025/03/testing-ansible-with-molecule/</id>
      <published>2025-03-28T00:00:00+00:00</published>
      <author>
        <name>Kannan Ponnusamy</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/03/testing-ansible-with-molecule/optical-disc-drive.webp&#34; alt=&#34;Photo of an open hard drive, with the data reading arm extended over the disc.&#34;&gt;&lt;br&gt;
&lt;a href=&#34;https://unsplash.com/photos/photo-of-optical-disc-drive-1iVKwElWrPA&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://unsplash.com/@heapdump&#34;&gt;Patrick Lindenberg&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-why-test-with-molecule&#34;&gt;1. Why Test with Molecule?&lt;/h3&gt;
&lt;p&gt;Molecule is a test framework for Ansible roles. It supports testing with multiple instances, operating systems and distributions, virtualization providers, test frameworks, and testing scenarios.&lt;/p&gt;
&lt;p&gt;Molecule is useful because it increases confidence in your automation and ensures roles are reliable. It catches issues early in the development cycle, reducing production problems. It also ensures consistency across different environments and platforms.&lt;/p&gt;
&lt;h3 id=&#34;2-install-prerequisites&#34;&gt;2. Install Prerequisites&lt;/h3&gt;
&lt;p&gt;Make sure you have Python &amp;gt;= 3.6 and pip installed. Then install Ansible and Molecule using pip:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install ansible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install molecule&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Molecule uses the &lt;code&gt;delegated&lt;/code&gt; driver by default. Other drivers can be installed separately from PyPI, most of them being included in the &lt;code&gt;molecule-plugins&lt;/code&gt; package. We are going to use the Docker driver, so let&amp;rsquo;s install that by running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install &amp;#34;molecule[docker]&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;3-ansible-monorepo-structure&#34;&gt;3. Ansible Monorepo Structure&lt;/h3&gt;
&lt;p&gt;We use the Ansible monorepo structure. This means our playbooks, variables, scripts, roles, plugins, inventory scripts, and configuration all reside and are version controlled together in the same repository.&lt;/p&gt;
&lt;p&gt;Here is the high level layout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;~/ansible-endpoint
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── ansible.cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── ansible_hosts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── molecule/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── playbooks/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── roles/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── apache_server/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── nginx_server/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We&amp;rsquo;ll focus on &lt;code&gt;roles/nginx_server&lt;/code&gt;, showing how to add and configure the Molecule scenarios for testing and also how to verify it.&lt;/p&gt;
&lt;h3 id=&#34;4-creating-your-first-molecule-scenario&#34;&gt;4. Creating Your First Molecule Scenario&lt;/h3&gt;
&lt;p&gt;Inside the &lt;code&gt;roles/nginx_server&lt;/code&gt; directory, let&amp;rsquo;s initialize the Molecule scenario:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd roles/nginx_server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;molecule init scenario --driver-name docker --scenario-name default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command will create the following files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roles/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── nginx_server/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── tasks/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │   └── main.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── molecule/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │   └── default/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │       ├── molecule.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │       ├── converge.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │       ├── verify.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    │       └── destroy.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here are some details about the files created by Molecule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;molecule.yml&lt;/code&gt; — The Molecule configuration file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;driver&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;docker&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;platforms&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;molecule-rocky9-${CI_JOB_ID}&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;image&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geerlingguy/docker-rockylinux9-ansible:latest&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;privileged&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;pre_build_image&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;volumes&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;- /sys/fs/cgroup:/sys/fs/cgroup:rw&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;cgroupns_mode&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;host&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;command&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;/usr/sbin/init&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;provisioner&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;ansible&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Key blocks from &lt;code&gt;molecule.yml&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;provisioner&lt;/code&gt; is the tool that will be used to provision the scenario. We are using Ansible to run the scenario itself.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;driver&lt;/code&gt; — As mentioned above, we are using Docker as the driver.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;platforms&lt;/code&gt; — Here we define the target platform for the scenario. We are using the Rocky 9 Docker image as the target platform. Thanks to &lt;a href=&#34;https://github.com/geerlingguy/docker-rockylinux9-ansible&#34;&gt;Jeff Geerling&lt;/a&gt; who created the Rocky 9 Docker image for Ansible testing with systemd in it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;converge.yml&lt;/code&gt; — The playbook to converge the scenario. This tells Molecule to apply the &lt;code&gt;nginx_server&lt;/code&gt; role to our test container (the &amp;ldquo;instance&amp;rdquo; defined in &lt;code&gt;molecule.yml&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Converge&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hosts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;all&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;gather_facts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tasks&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Run the nginx_server role&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hosts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;all&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;roles&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;- nginx_server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;verify.yml&lt;/code&gt; — The playbook to verify whether the converge was successful. This tells Molecule to verify that our role has been correctly installed on the Docker instance. Here we are going to check if NGINX is installed, running and also responding to requests.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Verify&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hosts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;all&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tasks&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Check if NGINX is installed&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ansible.builtin.package&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;nginx&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;state&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;present&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;check_mode&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;register&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;install&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;failed_when&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(install is changed) or (install is failed)&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Check if NGINX service is running&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ansible.builtin.service&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;nginx&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;state&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;started&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;enabled&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;check_mode&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;register&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;service&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;failed_when&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(service is changed) or (service is failed)&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Verify NGINX is up and running&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ansible.builtin.uri&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;url&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;http://localhost&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;status_code&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;200&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;destroy.yml&lt;/code&gt; — Destroys the instance defined in the &lt;code&gt;molecule.yml&lt;/code&gt; file. There won&amp;rsquo;t be any file, but the default Docker driver will automatically destroy the instance after the test is finished. If you want to override this behavior, you can add a &lt;code&gt;destroy.yml&lt;/code&gt; file to the scenario directory.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;5-running-molecule-tests&#34;&gt;5. Running Molecule tests&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A Molecule run consists of the various lifecycle events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;converge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;verify&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;destroy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are some of the important events in the Molecule lifecycle run. To run the full lifecycle Molecule run, we can use the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd roles/nginx_server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;molecule test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command executes the following steps in sequence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create: Spins up a test Rocky 9 container as mentioned in the &lt;code&gt;molecule.yml&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Converge: Applies the &lt;code&gt;nginx_server&lt;/code&gt; role inside the container.&lt;/li&gt;
&lt;li&gt;Verify: Verifies whether the NGINX server is installed, running, and responding to requests.&lt;/li&gt;
&lt;li&gt;Destroy: Cleans up the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can also run each step individually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;molecule create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;molecule converge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;molecule verify&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;molecule destroy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To avoid rebuilding the containers, we can run commands from converge step.&lt;/p&gt;
&lt;h3 id=&#34;6-integrating-molecule-with-gitlab-cicd&#34;&gt;6. Integrating Molecule with GitLab CI/CD&lt;/h3&gt;
&lt;p&gt;We want to run the Molecule tests whenever a merge request which changes the particular role is created.&lt;/p&gt;
&lt;p&gt;To automatically run the Molecule tests, we can use the GitLab CI/CD.&lt;/p&gt;
&lt;p&gt;Here is an example of a &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file that runs our Molecule tests in the Docker based environment for our &lt;code&gt;nginx_server&lt;/code&gt; role.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;stages&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;- test&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;before_script&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;- pip install molecule ansible docker molecule-docker&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;molecule_test&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;stage&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;test&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;variables&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ROLE_PATH&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;roles/nginx_server&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- cd $ROLE_PATH&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- molecule test&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;rules&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;changes&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;- $ROLE_PATH/**&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this setup, we&amp;rsquo;ve set up a Molecule scenario for a specific Ansible role, then run tests both manually and automatically via GitLab CI. This allows tests to trigger whenever a merge request modifies that role, ensuring a quick feedback loop and a higher degree of reliability in the automation.&lt;/p&gt;
&lt;p&gt;To learn more about Molecule, here are some useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ansible.readthedocs.io/projects/molecule/&#34;&gt;Molecule documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ansible/molecule&#34;&gt;Molecule GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ansible-community/molecule-plugins&#34;&gt;Molecule driver plugins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Middleware: Is that still a thing?</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/05/middleware-is-that-still-a-thing/"/>
      <id>https://www.endpointdev.com/blog/2022/05/middleware-is-that-still-a-thing/</id>
      <published>2022-05-26T00:00:00+00:00</published>
      <author>
        <name>Richard Templet</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/05/middleware-is-that-still-a-thing/20220401-013133.webp&#34; alt=&#34;Photo looking from ground level up at concrete and crushed stone building against a blue sky with some white clouds&#34;&gt;&lt;/p&gt;
&lt;!-- photo by Jon Jensen --&gt;
&lt;p&gt;The simple answer to the question in the title is simply, yes! Despite the term being many decades old and well past its hype peak, middleware is still very much a thing and has become a key part of the technical landscape that is critical in day-to-day functioning of systems.&lt;/p&gt;
&lt;p&gt;So there are still some questions to be answered: What is middleware? What does it do? And maybe most importantly: Why do we care?&lt;/p&gt;
&lt;h3 id=&#34;what-is-it&#34;&gt;What is it?&lt;/h3&gt;
&lt;p&gt;In its simplest meaning, middleware is an application that sits between other applications and shuffles data between them. There is normally one system requesting the information and the middleware figures out where to get that requested data and makes the request to another system.&lt;/p&gt;
&lt;p&gt;An easy example of this is buying something at a retail store using your credit or debit card. When you swipe your card, the business makes a request to a service (some middleware) to ask if there&amp;rsquo;s enough room on the card for that purchase. Then that system makes a request to the appropriate bank or card holding company to ask the same question. The bank or card holding company replies with either a yes or no and that answer is then relayed back to the terminal where you swiped your card.&lt;/p&gt;
&lt;p&gt;You might be thinking, “Why do we need the middleware? Just have the terminal ask the right bank!” Well, that’s a benefit of middleware: The terminal software doesn’t need to have all the same logic coded into it to know whether or how to talk to Visa or Mastercard as well as Citibank or Capital One or Bank of America to get the answer. It just talks to the middleware and lets it figure all of that out. This way you can have many different types of terminals or credit card taking applications available but only one middleware application needs to have the smarts and authorization to know where and how to get the answer.&lt;/p&gt;
&lt;p&gt;The term middleware is most often used to describe business systems internal to a company. It is a subset of the broader concept &amp;ldquo;API&amp;rdquo; (application programming interface), which includes services provided for external users, either the general public or specific authorized customers or business partners. If a service is used directly by humans of the general public, it is typically called SaaS (software as a service).&lt;/p&gt;
&lt;h3 id=&#34;what-does-it-do&#34;&gt;What does it do?&lt;/h3&gt;
&lt;p&gt;As I explained in the example above, the simplistic definition of middleware is an application that shuffles data between two systems. In reality, middleware is sometimes way more complicated than that!&lt;/p&gt;
&lt;p&gt;In that example of a credit card charge terminal, using middleware also allows for the retailer to set centralized company-wide rules for fraud screening, add or remove supported payment methods, or change which payment gateway is used, without having to update each terminal individually.&lt;/p&gt;
&lt;p&gt;Middleware also can do data manipulation and mapping between two systems. Let’s consider a more complicated case like booking a cross-country flight.&lt;/p&gt;
&lt;p&gt;You’ve probably used sites like Google Flights or Expedia to search for ticket costs. You go to their site, put in your starting airport(s) and your destination airport(s), select the dates when you’d like to travel, and maybe check the box saying you’ve got some wiggle room on when you leave and come back. After that you click the search button and within a few seconds, you’ve got tons of flight options to sort through! The search results can be sorted by price, number of stops, and airlines, to name a few.&lt;/p&gt;
&lt;p&gt;So taking a step back to marvel at what just happened, how do you think they got that much data from that many sources so quickly? Well, the answer is middleware.&lt;/p&gt;
&lt;p&gt;At some point, a middleware system made a request to each of the airlines to ask for their available flights with their dates, costs, amenities, etc. and stored that information on their own system to be used for your search. There are many different ways that data could be captured. We won’t delve into all that excitement, but what we do know is some system (middleware) had to fetch this data from each of the airlines and store it to be used for your search. It has to organize and store that data in a way that makes sense to the search engine to use so you can get back your search results in a speedy fashion.&lt;/p&gt;
&lt;p&gt;There’s a good chance that the data from American Airlines will differ in its structure and data points compared to Delta. It’s the middleware’s job to take each of those data feeds and organize and store them somewhere for the search engine to use. In this part of the example, the middleware is taking a request to fetch the Delta flight data, turning that request into a request to another system for the data then doing whatever data manipulation and storage of that data to satisfy the search engine.&lt;/p&gt;
&lt;h3 id=&#34;why-do-we-care&#34;&gt;Why do we care?&lt;/h3&gt;
&lt;p&gt;The main reason that we care is we’ve become used to having a single location (website, application etc.) that allows us access to data from multiple places.&lt;/p&gt;
&lt;p&gt;In our credit card example, there are many different types of credit cards we can use to check out at our local gas station. Having them require us to only use a Shell gas card would be bad for their business! We’ve grown accustomed to being able to use our Visa, Mastercard, American Express, or Shell gas card at that same gas station. This is all made possible due to middleware behind the scenes doing the hard work.&lt;/p&gt;
&lt;p&gt;In many cases, the function of gathering data from many sources in one place, keeping it current, and allowing it to be searched, sorted, etc. is not just one means to an end, but it actually &lt;em&gt;is&lt;/em&gt; the whole business! It is why services like Google Flights and Expedia exist.&lt;/p&gt;
&lt;p&gt;Middleware can serve many other purposes even when only used internally by a single company:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;authentication&lt;/strong&gt; — making sure who- or whatever made a request is supposed to be able to&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;authorization&lt;/strong&gt; — making sure the kind of request being made is one this user is allowed to&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;auditing&lt;/strong&gt; — logging or sampling traffic in middleware is often more comprehensive and convenient than at each service individually&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rate-limiting&lt;/strong&gt; — ensuring the user is not using too many resources too fast, or that one service isn’t overused and interfering with access to other services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caching&lt;/strong&gt; — storing answers to frequent requests to reuse for a limited time, reducing the number of live requests to busy internal applications and databases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;billing&lt;/strong&gt; — tracking usage so it can be paid for or at least each user’s usage is fairly recorded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fault-tolerance&lt;/strong&gt; — retrying a request if an internal system is temporarily unavailable, or rerouting requests to a different system, to shield users from outages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;load balancing&lt;/strong&gt; — spreading requests across multiple services to allow greater total throughput&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;translation&lt;/strong&gt; — modern APIs typically use JSON to structure their data, and middleware can translate between a nice JSON interface and older systems using a variety of more complex and archaic data formats&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;migrations&lt;/strong&gt; — transparently moving from an old system to a newer one, possibly in phases or temporarily for testing, without users having to change anything or even be aware&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In closing, while middleware isn’t commonly mentioned or even thought about, it is a very important part of what makes the technological world function.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Creating Telegram bots with Google Apps Script</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/"/>
      <id>https://www.endpointdev.com/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/</id>
      <published>2022-01-17T00:00:00+00:00</published>
      <author>
        <name>Muhammad Najmi bin Ahmad Zabidi</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/20201110-165127-sm.jpg&#34; alt=&#34;Empty clothes in the shape of a human sitting on a bench with fall leaves&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Jon Jensen --&gt;
&lt;p&gt;In a previous post on this blog, Afif wrote about &lt;a href=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/&#34;&gt;how to use Google Apps Script with Google Forms&lt;/a&gt;. Coincidentally, last year I learned a bit about how to use Google Apps Script with Telegram Bot as a personal ledger tool, as outlined in &lt;a href=&#34;https://medium.com/@mars_escobin/telegram-inline-keyboards-using-google-app-script-f0a0550fde26&#34;&gt;this post by Mars Escobin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Telegram Bot I created from Mars’s code looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/najmi-budget.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this post I will share a bit on how to adapt Mars’s code and use Telegram Bot to get the input from the user and let Google Apps Script call Google’s cloud-based services (translation and finance) to later return the outputs to the user.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/botfather.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The initial process of creating a Telegram bot is outlined &lt;a href=&#34;https://core.telegram.org/bots&#34;&gt;on Telegram’s website&lt;/a&gt;. After receiving Telegram’s API key we can use it inside our Google Apps Script editor.&lt;/p&gt;
&lt;h3 id=&#34;translation-bot&#34;&gt;Translation Bot&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/najmi-translation.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/najmi_translation_spreadsheet.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;One way to use the Telegram Bot and Google Cloud Services which came to mind was to create a translation bot. Although there are undoubtedly tons of mobile apps out there which do the same thing, I wanted to learn about it by using Telegram. So I found a class that I could use in Apps Script to realize that.&lt;/p&gt;
&lt;p&gt;Google Apps Script can be used to manipulate the Google Translate capability by calling the &lt;a href=&#34;https://developers.google.com/apps-script/reference/language/language-app&#34;&gt;&lt;code&gt;Language.App&lt;/code&gt; class&lt;/a&gt;. The translation capability is invoked with &lt;code&gt;LanguageApp.translate(text, sourceLanguage, targetLanguage)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In addition to fetching the input from the users, I also wanted to store the searched word inside a Google Sheets spreadsheet.&lt;/p&gt;
&lt;p&gt;So by using this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sheet.appendRow([formattedDate, item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;], myTranslationOutput]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;we can append the searched word inside the Google spreadsheet, and later append the input and output which we got from the user.&lt;/p&gt;
&lt;p&gt;In my case, I removed the inline &lt;code&gt;keyBoard&lt;/code&gt; function from Mars’s code since I didn’t plan to use it in my bot. Instead, I will use the inputs which were sent through the &lt;code&gt;item&lt;/code&gt; variable and let &lt;code&gt;LanguageApp&lt;/code&gt; translate it. The translation will virtually take a few seconds, so if we do not put an interval within our code, &lt;code&gt;sendMessage&lt;/code&gt;  will reply with an empty output for the translation.&lt;/p&gt;
&lt;p&gt;In order to handle this, I used the &lt;code&gt;Utilities.sleep()&lt;/code&gt; function prior to &lt;code&gt;return sendMessage()&lt;/code&gt; so that I will be able to grab the answer before returning the output to the requestor.&lt;/p&gt;
&lt;p&gt;The following are my changes for creating the translation bot:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; token = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert your Telegram API token here&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; telegramUrl = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://api.telegram.org/bot&amp;#34;&lt;/span&gt; + token;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; webAppUrl = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert your webAppURL which is generated from Google Apps Script UI here&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; sendMessage(id, text) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; data = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    method: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payload: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      method: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sendMessage&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      chat_id: &lt;span style=&#34;color:#038&#34;&gt;String&lt;/span&gt;(id),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      text: text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      parse_mode: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;HTML&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UrlFetchApp.fetch(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://api.telegram.org/bot&amp;#39;&lt;/span&gt; + token + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;, data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; doPost(e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; contents = JSON.parse(e.postData.contents);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; ssId = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert your webAppURL which is generated from Google Apps Script UI here&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; sheet = SpreadsheetApp.openById(ssId).getSheetByName(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;sheet name here&amp;gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (contents.message) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; id = contents.message.from.id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; text = contents.message.text;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (text.indexOf(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;) !== -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; dateNow = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;Date&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; formattedDate = dateNow.getDate() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt; + (dateNow.getMonth() + &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; item = text.split(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; myTranslationOutput = LanguageApp.translate(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.appendRow([formattedDate, item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;], myTranslationOutput]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      Utilities.sleep(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;200&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; sendMessage(id, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;The translation of &amp;#34;&lt;/span&gt; + item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; is &amp;#34;&lt;/span&gt; + myTranslationOutput);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; sendMessage(id, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;The word that you key in will be kept for our analysis purpose\nPlease use this format : word, source language code, target language code \nRefer cloud.google.com/translate/docs/languages&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;currency-converter-bot&#34;&gt;Currency Converter Bot&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/najmi-currency.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/creating-translation-currency-converter-telegram-google-apps/najmi_currency_spreadsheet.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are at least two possible ways to create a currency converter bot with Telegram and Google Apps Script. I could either invoke a curl-like method by calling an external API (which is not related to Google) or just manipulating whatever Google Finance offers. I did some searching but I could not find any built-in class in order to do the conversion within the code. However, I remember that Google Sheets could actually call Google Finance within its cell. So I decided to let Sheets do the conversion and then I will fetch the result and return it to the requester.&lt;/p&gt;
&lt;p&gt;This is shown in the following snippet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;a2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;b2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;c2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;).setValue(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;=GOOGLEFINANCE(&amp;#34;currency:&amp;#34;&amp;amp;b2&amp;amp;c2)*a2&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And later we fetched the value from &lt;code&gt;d2&lt;/code&gt; cell with&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; value = SpreadsheetApp.getActiveSheet().getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;).getValue();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As the default value is taking many decimal points, I made it fixed to two decimal points.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;value = value.toFixed(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I then returned the value which later converted the currency code to uppercase with &lt;code&gt;.toUpperCase()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; sendMessage(id, item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;].toUpperCase() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; + item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; = &amp;#34;&lt;/span&gt; + item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;].toUpperCase() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; + value);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see my changes in the following scripts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; token = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert your Telegram API token here&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; telegramUrl = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://api.telegram.org/bot&amp;#34;&lt;/span&gt; + token;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; webAppUrl = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert your webAppURL which is generated from Google Apps Script UI here&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; setWebhook() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; url = telegramUrl + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/setWebhook?url=&amp;#34;&lt;/span&gt; + webAppUrl;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; response = UrlFetchApp.fetch(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Logger.log(response.getContentText());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; sendMessage(id, text) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; data = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    method: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payload: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      method: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sendMessage&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      chat_id: &lt;span style=&#34;color:#038&#34;&gt;String&lt;/span&gt;(id),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      text: text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      parse_mode: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;HTML&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UrlFetchApp.fetch(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://api.telegram.org/bot&amp;#39;&lt;/span&gt; + token + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;, data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; doPost(e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; contents = JSON.parse(e.postData.contents);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; ssId = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;insert the spreadsheet ID here, you can get it from the browser URL bar&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; sheet = SpreadsheetApp.openById(ssId).getSheetByName(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;sheet name here&amp;gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (contents.message) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; id = contents.message.from.id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; text = contents.message.text;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (text.indexOf(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;) !== -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; dateNow = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;Date&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; formattedDate = dateNow.getDate() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt; + (dateNow.getMonth() + &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; item = text.split(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;a2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;b2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;c2&amp;#39;&lt;/span&gt;).setValue(item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;).setValue(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;=GOOGLEFINANCE(&amp;#34;currency:&amp;#34;&amp;amp;b2&amp;amp;c2)*a2&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      SpreadsheetApp.getActiveSheet().getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;c2&amp;#39;&lt;/span&gt;).setValue(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;=GOOGLEFINANCE(&amp;#34;currency:&amp;#34;&amp;amp;item[1]&amp;amp;item[2])*item[0]&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; value = SpreadsheetApp.getActiveSheet().getRange(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;).getValue();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      Utilities.sleep(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      sheet.appendRow([formattedDate, item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;], item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;], value]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      value = value.toFixed(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; sendMessage(id, item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;].toUpperCase() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; + item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; = &amp;#34;&lt;/span&gt; + item[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;].toUpperCase() + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; + value)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; sendMessage(id, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;The word that you key in will be kept for our analysis purpose\nPlease use this format : amount, source currency code, target currency code&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;special-notes&#34;&gt;Special Notes&lt;/h3&gt;
&lt;p&gt;Throughout the process, I found several ways for us to refer to a spreadsheet that we want inside the code. For example, we can use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; ssId = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;spreadsheet&amp;#39;s ID&amp;gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; sheet = SpreadsheetApp.openById(ssId).getSheetByName(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;the sheet&amp;#39;s name&amp;gt;&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can select a specific sheet, if your spreadsheet file contains multiple different sheets (tabs).&lt;/p&gt;
&lt;p&gt;Or we could use &lt;code&gt;SpreadsheetApp.getActiveSpreadsheet()&lt;/code&gt; but this method depends on the active sheet inside the spreadsheet&amp;rsquo;s UI, as described &lt;a href=&#34;https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#getactivesheet&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nevertheless, both of the methods above are part of the &lt;code&gt;SpreadsheetApp&lt;/code&gt; Class.&lt;/p&gt;
&lt;p&gt;There are many more things that could be done by the Google Apps Script. It is really helpful for automating anything that we routinely do across many files. In my example that I gave above, the function is only being used on two different spreadsheets — as a placeholder so that I could get the result to be returned to my Telegram bot.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Database integration testing with .NET</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/"/>
      <id>https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/</id>
      <published>2022-01-12T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/database-integration-testing-with-dotnet/banner.jpg&#34; alt=&#34;Sunset over lake in mountains&#34;&gt;&lt;/p&gt;
&lt;!-- Image by Zed Jensen, 2021 --&gt;
&lt;p&gt;&lt;a href=&#34;https://rubyonrails.org/&#34;&gt;Ruby on Rails&lt;/a&gt; is great. We use it at End Point for many projects with great success. One of Rails’ cool features is how easy it is to write database integration tests. Out of the box, Rails projects come with all the configuration necessary to set up a database that&amp;rsquo;s exclusive for the automated test suite. This database includes all the tables and other objects that exist within the regular database that the app uses during its normal execution. So, it is very easy to write automated tests that cover the application components that interact with the database.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://dotnet.microsoft.com/en-us/apps/aspnet&#34;&gt;ASP.NET Core&lt;/a&gt; is also great! However, it doesn&amp;rsquo;t have this feature out of the box. Let&amp;rsquo;s see if we can&amp;rsquo;t do it ourselves.&lt;/p&gt;
&lt;h3 id=&#34;the-sample-project&#34;&gt;The sample project&lt;/h3&gt;
&lt;p&gt;As a sample project we will use a REST API that I wrote for &lt;a href=&#34;/blog/2021/07/dotnet-5-web-api/&#34;&gt;another article&lt;/a&gt;. Check it out if you want to learn more about the ins and outs of developing REST APIs with .NET. You can find the source code &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api&#34;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The API is very straightforward. It provides a few endpoints for &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Glossary/CRUD&#34;&gt;CRUDing&lt;/a&gt; some database tables. It also provides an endpoint which, when given some vehicle information, will calculate a monetary value for that vehicle. That&amp;rsquo;s a feature that would be interesting for us to cover with some tests.&lt;/p&gt;
&lt;p&gt;The logic for that feature is backed by a specific class and it depends heavily on database interactions. As such, that class is a great candidate for writing a few automated integration tests against. The class in question is &lt;code&gt;QuotesService&lt;/code&gt; which is defined in &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Services/QuoteService.cs&#34;&gt;Services/QuoteService.cs&lt;/a&gt;. The class provides features for fetching records from the database (the &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Services/QuoteService.cs#L23&#34;&gt;&lt;code&gt;GetAllQuotes&lt;/code&gt;&lt;/a&gt; method) as well as creating new records based on data from the incoming request and a set of rules stored in the database itself (the &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Services/QuoteService.cs#L53&#34;&gt;&lt;code&gt;CalculateQuote&lt;/code&gt;&lt;/a&gt; method).&lt;/p&gt;
&lt;p&gt;In order to add automated tests, the first step is to organize our project so that it supports them. Let&amp;rsquo;s do that next.&lt;/p&gt;
&lt;h3 id=&#34;organizing-the-source-code-to-allow-for-automated-testing&#34;&gt;Organizing the source code to allow for automated testing&lt;/h3&gt;
&lt;p&gt;In general, the source code of most real world .NET applications is organized as one or more &amp;ldquo;projects&amp;rdquo; under one &amp;ldquo;solution&amp;rdquo;. A solution is a collection of related projects, and a project is something that produces a deployment artifact. An artifact is a library (i.e. a &lt;code&gt;*.dll&lt;/code&gt; file) or something that can be executed like a console or web app.&lt;/p&gt;
&lt;p&gt;Our sample app is a stand-alone &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0&amp;amp;tabs=visual-studio-code&#34;&gt;&amp;ldquo;webapi&amp;rdquo; project&lt;/a&gt;, meaning that it&amp;rsquo;s not within a solution. For automated tests, however, we need to create a new project for tests, parallel to our main one. Now that we have two projects instead of one, we need to reorganize the sample app&amp;rsquo;s source code to comply with the &amp;ldquo;projects in a solution&amp;rdquo; structure I mentioned earlier.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by moving all the files in the root directory into a new &lt;code&gt;VehicleQuotes&lt;/code&gt; directory. That&amp;rsquo;s one project. Then, we create a new automated tests project by running the following, still from the root directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet new xunit -o VehicleQuotes.Tests&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That creates a new automated tests project named &lt;code&gt;VehicleQuotes.Tests&lt;/code&gt; (under a new aptly-named &lt;code&gt;VehicleQuotes.Tests&lt;/code&gt; directory) which uses the &lt;a href=&#34;https://xunit.net&#34;&gt;xUnit.net&lt;/a&gt; test framework. There are other options when it comes to test frameworks in .NET, such as &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest&#34;&gt;MSTest&lt;/a&gt; and &lt;a href=&#34;https://nunit.org/&#34;&gt;NUnit&lt;/a&gt;. We&amp;rsquo;re going to use xUnit.net, but the others should work just as well for our purposes.&lt;/p&gt;
&lt;p&gt;Now, we need to create a new solution to contain those two projects. Solutions come in the form of &lt;code&gt;*.sln&lt;/code&gt; files and we can create ours like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet new sln -o vehicle-quotes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That should&amp;rsquo;ve created a new &lt;code&gt;vehicle-quotes.sln&lt;/code&gt; file for us. We should now have a file structure like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── vehicle-quotes.sln
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── VehicleQuotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── VehicleQuotes.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── VehicleQuotes.Tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── VehicleQuotes.Tests.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Like I said, the &lt;code&gt;*.sln&lt;/code&gt; file indicates that this is a solution. The &lt;code&gt;*.csproj&lt;/code&gt; files identify the individual projects that make up the solution.&lt;/p&gt;
&lt;p&gt;Now, we need to tell dotnet that those two projects belong in the same solution. These commands do that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet sln add ./VehicleQuotes/VehicleQuotes.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet sln add ./VehicleQuotes.Tests/VehicleQuotes.Tests.csproj&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we update the &lt;code&gt;VehicleQuotes.Tests&lt;/code&gt; project so that it references the &lt;code&gt;VehicleQuotes&lt;/code&gt; project. That way, the test suite will have access to all the classes defined in the REST API. Here&amp;rsquo;s the command for that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet add ./VehicleQuotes.Tests/VehicleQuotes.Tests.csproj reference ./VehicleQuotes/VehicleQuotes.csproj&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With all that setup out of the way, we can now start writing some tests.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can learn more about project organization in the &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/tutorials/testing-with-cli&#34;&gt;official online documentation&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;creating-a-dbcontext-instance-to-talk-to-the-database&#34;&gt;Creating a DbContext instance to talk to the database&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;VehicleQuotes.Tests&lt;/code&gt; automated tests project got created with a default test file named &lt;code&gt;UnitTest1.cs&lt;/code&gt;. You can delete it or ignore it, since we will not use it.&lt;/p&gt;
&lt;p&gt;In general, it&amp;rsquo;s a good idea for the test project to mimic the directory structure of the project that it will be testing. Also, we already decided that we would focus our test efforts on the &lt;code&gt;QuoteService&lt;/code&gt; class from the &lt;code&gt;VehicleQuotes&lt;/code&gt; project. That class is defined in &lt;code&gt;VehicleQuotes/Services/QuoteService.cs&lt;/code&gt;, so let&amp;rsquo;s create a similarly located file within the test project which will contain the test cases for that class. Here: &lt;code&gt;VehicleQuotes.Tests/Services/QuoteServiceTests.cs&lt;/code&gt;. These would be the contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.Tests/Services/QuoteServiceTests.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Xunit&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Tests.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteServiceTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; GetAllQuotesReturnsEmptyWhenThereIsNoDataStored()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Given&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// When&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the basic structure for tests using xUnit.net. Any method annotated with a &lt;code&gt;[Fact]&lt;/code&gt; attribute will be picked up and run by the test framework. In this case, I&amp;rsquo;ve created one such method called &lt;code&gt;GetAllQuotesReturnsEmptyWhenThereIsNoDataStored&lt;/code&gt; which should give away its intention. This test case will validate that &lt;code&gt;QuoteService&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetAllQuotes&lt;/code&gt; method returns an empty set when called with no data in the database.&lt;/p&gt;
&lt;p&gt;Before we can write this test case, though, the suite needs access to the test database. Our app uses &lt;a href=&#34;https://docs.microsoft.com/en-us/ef/core/&#34;&gt;Entity Framework Core&lt;/a&gt; for database interaction, which means that the database is accessed via a &lt;code&gt;DbContext&lt;/code&gt; class. Looking at the source code of our sample app, we can see that the &lt;code&gt;DbContext&lt;/code&gt; being used is &lt;code&gt;VehicleQuotesContext&lt;/code&gt;, defined in &lt;code&gt;VehicleQuotes/Data/VehicleQuotesContext.cs&lt;/code&gt;. Let&amp;rsquo;s add a utility method to the &lt;code&gt;QuoteServiceTests&lt;/code&gt; class which can be used to create new instances of &lt;code&gt;VehicleQuotesContext&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.Tests/Services/QuoteServiceTests.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Services&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Tests.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteServiceTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; VehicleQuotesContext CreateDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; options = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; DbContextOptionsBuilder&amp;lt;VehicleQuotesContext&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .UseNpgsql(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Host=db;Database=vehicle_quotes_test;Username=vehicle_quotes;Password=password&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .Options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; context = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; VehicleQuotesContext(options);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            context.Database.EnsureCreated();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, we need to go through three steps to create the &lt;code&gt;VehicleQuotesContext&lt;/code&gt; instance and get a database that&amp;rsquo;s ready for testing:&lt;/p&gt;
&lt;p&gt;First, we create a &lt;code&gt;DbContextOptionsBuilder&lt;/code&gt; and use that to obtain the &lt;code&gt;options&lt;/code&gt; object that the &lt;code&gt;VehicleQuotesContext&lt;/code&gt; needs as a constructor parameter. We needed to include the &lt;code&gt;Microsoft.EntityFrameworkCore&lt;/code&gt; namespace in order to have access to the &lt;code&gt;DbContextOptionsBuilder&lt;/code&gt;. For this, I just copied and slightly modified this statement from the &lt;code&gt;ConfigureServices&lt;/code&gt; method in the REST API&amp;rsquo;s &lt;code&gt;VehicleQuotes/Startup.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes/Startup.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddDbContext&amp;lt;VehicleQuotesContext&amp;gt;(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .UseNpgsql(Configuration.GetConnectionString(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;VehicleQuotesContext&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .UseLoggerFactory(LoggerFactory.Create(builder =&amp;gt; builder.AddConsole()))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .EnableSensitiveDataLogging()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a method that runs when the application is starting up to set up all the services that the app uses to work. Here, it&amp;rsquo;s setting up the &lt;code&gt;DbContext&lt;/code&gt; to enable database interaction. For the test suite, I took this statement as a starting point and removed the logging configurations and specified a hardcoded connection string that specifically points to a new &lt;code&gt;vehicle_quotes_test&lt;/code&gt; database that will be used for testing.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re following along, then you need a PostgreSQL instance that you can use to run the tests. In my case, I have one running that is reachable via the connection string I specified: &lt;code&gt;Host=db;Database=vehicle_quotes_test;Username=vehicle_quotes;Password=password&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you have Docker, a quick way to get a Postgres database up and running is with this command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run -d &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    --name vehicle-quotes-db &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    -p 5432:5432 &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    --network host &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    -e &lt;span style=&#34;color:#369&#34;&gt;POSTGRES_DB&lt;/span&gt;=vehicle_quotes &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    -e &lt;span style=&#34;color:#369&#34;&gt;POSTGRES_USER&lt;/span&gt;=vehicle_quotes &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    -e &lt;span style=&#34;color:#369&#34;&gt;POSTGRES_PASSWORD&lt;/span&gt;=password &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    postgres&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;rsquo;ll spin up a new Postgres instance that&amp;rsquo;s reachable via &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Secondly, now that we have the options parameter ready, we can quite simply instantiate a new &lt;code&gt;VehicleQuotesContext&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; context = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; VehicleQuotesContext(options);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we call the &lt;code&gt;EnsureCreated&lt;/code&gt; method so that the database that we specified in the connection string is actually created.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.Database.EnsureCreated();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the database that our test suite will use.&lt;/p&gt;
&lt;h3 id=&#34;defining-the-test-database-connection-string-in-the-appsettingsjson-file&#34;&gt;Defining the test database connection string in the appsettings.json file&lt;/h3&gt;
&lt;p&gt;One quick improvement that we can do to the code we&amp;rsquo;ve written so far is move the connection string for the test database into a separate configuration file, instead of having it hardcoded. Let&amp;rsquo;s do that next.&lt;/p&gt;
&lt;p&gt;We need to create a new &lt;code&gt;appsettings.json&lt;/code&gt; file under the &lt;code&gt;VehicleQuotes.Tests&lt;/code&gt; directory. Then we have to add the connection string like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;ConnectionStrings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;VehicleQuotesContext&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Host=db;Database=vehicle_quotes_test;Username=vehicle_quotes;Password=password&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the standard way of configuring connection strings in .NET. Now, to actually fetch this value from within our test suite code, we make the following changes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+using Microsoft.Extensions.Hosting;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+using Microsoft.Extensions.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+using Microsoft.Extensions.DependencyInjection;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;namespace VehicleQuotes.Tests.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    public class QuoteServiceTests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        private VehicleQuotesContext CreateDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+           var host = Host.CreateDefaultBuilder().Build();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+           var config = host.Services.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            var options = new DbContextOptionsBuilder&amp;lt;VehicleQuotesContext&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-               .UseNpgsql(&amp;#34;Host=db;Database=vehicle_quotes_test;Username=vehicle_quotes;Password=password&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+               .UseNpgsql(config.GetConnectionString(&amp;#34;VehicleQuotesContext&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;                .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .Options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            var context = new VehicleQuotesContext(options);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            context.Database.EnsureCreated();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            return context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First we add a few &lt;code&gt;using&lt;/code&gt; statements. We need &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt; so that we can have access to the &lt;code&gt;Host&lt;/code&gt; class through which we obtain access to the application&amp;rsquo;s execution context. This allows us to access the built-in configuration service. We also need &lt;code&gt;Microsoft.Extensions.Configuration&lt;/code&gt; to have access to the &lt;code&gt;IConfiguration&lt;/code&gt; interface which is how we reference the configuration service which allows us access to the &lt;code&gt;appsettings.json&lt;/code&gt; config file. And we also need the &lt;code&gt;Microsoft.Extensions.DependencyInjection&lt;/code&gt; namespace which allows us to tap into the built-in dependency injection mechanism, through which we can access the default configuration service I mentioned before. Specifically, that namespace is where the &lt;code&gt;GetRequiredService&lt;/code&gt; extension method lives.&lt;/p&gt;
&lt;p&gt;All this translates into the few code changes that you see in the previous diff: first getting the app&amp;rsquo;s host, then getting the configuration service, then using that to fetch our connection string.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can refer to &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration&#34;&gt;the official documentation&lt;/a&gt; to learn more about configuration in .NET.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;writing-a-simple-test-case-that-fetches-data&#34;&gt;Writing a simple test case that fetches data&lt;/h3&gt;
&lt;p&gt;Now that we have a way to access the database from within the test suite, we can finally write an actual test case. Here&amp;rsquo;s the &lt;code&gt;GetAllQuotesReturnsEmptyWhenThereIsNoDataStored&lt;/code&gt; one that I alluded to earlier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Tests.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteServiceTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; GetAllQuotesReturnsEmptyWhenThereIsNoDataStored()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Given&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; dbContext = CreateDbContext();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; service = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; QuoteService(dbContext, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// When&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; service.GetAllQuotes();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.Empty(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This one is a very simple test. We obtain a new &lt;code&gt;VehicleQuotesContext&lt;/code&gt; instance that we can use to pass as a parameter when instantiating the component that we want to test: the &lt;code&gt;QuoteService&lt;/code&gt;. We then call the &lt;code&gt;GetAllQuotes&lt;/code&gt; method and assert that it returned an empty set. The test database was just created, so there should be no data in it, hence the empty resource set.&lt;/p&gt;
&lt;p&gt;To run this test, we do &lt;code&gt;dotnet test&lt;/code&gt;. I personally like a more verbose output so I like to use this variant of the command: &lt;code&gt;dotnet test --logger &amp;quot;console;verbosity=detailed&amp;quot;&lt;/code&gt;. Here&amp;rsquo;s what the output looks like.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ dotnet test --logger &amp;#34;console;verbosity=detailed&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  All projects are up-to-date for restore.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  VehicleQuotes -&amp;gt; /app/VehicleQuotes/bin/Debug/net5.0/VehicleQuotes.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  VehicleQuotes.Tests -&amp;gt; /app/VehicleQuotes.Tests/bin/Debug/net5.0/VehicleQuotes.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Test run for /app/VehicleQuotes.Tests/bin/Debug/net5.0/VehicleQuotes.Tests.dll (.NETCoreApp,Version=v5.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Microsoft (R) Test Execution Command Line Tool Version 16.11.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Copyright (c) Microsoft Corporation.  All rights reserved.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Starting test execution, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;A total of 1 test files matched the specified pattern.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/app/VehicleQuotes.Tests/bin/Debug/net5.0/VehicleQuotes.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.3+1b45f5407b (64-bit .NET 5.0.12)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[xUnit.net 00:00:01.03]   Discovering: VehicleQuotes.Tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[xUnit.net 00:00:01.06]   Discovered:  VehicleQuotes.Tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[xUnit.net 00:00:01.06]   Starting:    VehicleQuotes.Tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[xUnit.net 00:00:03.25]   Finished:    VehicleQuotes.Tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Passed VehicleQuotes.Tests.Services.QuoteServiceTests.GetAllQuotesReturnsEmptyWhenThereIsNoDataStored [209 ms]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Test Run Successful.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Total tests: 1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     Passed: 1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Total time: 3.7762 Seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;resetting-the-state-of-the-database-after-each-test&#34;&gt;Resetting the state of the database after each test&lt;/h3&gt;
&lt;p&gt;Now we need to write a test that actually writes data into the database. However, every test case needs to start with the database in its original state. In other words, the changes that one test case does to the test database should not be seen, affect, or be expected by any subsequent test. That will make it so our test cases are isolated and repeatable. That&amp;rsquo;s not possible with our current implementation, though.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can read more about the FIRST principles of testing &lt;a href=&#34;https://medium.com/@tasdikrahman/f-i-r-s-t-principles-of-testing-1a497acda8d6&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Luckily, that&amp;rsquo;s a problem that&amp;rsquo;s easily solved with Entity Framework Core. All we need to do is call a method that ensures that the database is deleted just before it ensures that it is created. Here&amp;rsquo;s what it looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; private VehicleQuotesContext CreateDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     var host = Host.CreateDefaultBuilder().Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     var config = host.Services.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     var options = new DbContextOptionsBuilder&amp;lt;VehicleQuotesContext&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         .UseNpgsql(config.GetConnectionString(&amp;#34;VehicleQuotesContext&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         .Options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     var context = new VehicleQuotesContext(options);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    context.Database.EnsureDeleted();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     context.Database.EnsureCreated();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     return context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that&amp;rsquo;s all. Now every test case that calls &lt;code&gt;CreateDbContext&lt;/code&gt; in order to obtain a &lt;code&gt;DbContext&lt;/code&gt; instance will effectively trigger a database reset. Feel free to &lt;code&gt;dotnet test&lt;/code&gt; again to validate that the test suite is still working.&lt;/p&gt;
&lt;p&gt;Now, depending on the size of the database, this can be quite expensive. For integration tests, performance is not as big of a concern as for unit tests. This is because integration tests should be fewer in number and less frequently run.&lt;/p&gt;
&lt;p&gt;We can make it better though. Instead of deleting and recreating the database before each test case, we&amp;rsquo;ll take a page out of Ruby on Rails’ book and run each test case within a database transaction which gets rolled back after the test is done. For now though, let&amp;rsquo;s write another test case: this time, one where we insert new records into the database.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to hear a more in-depth discussion about automated testing in general, I go into further detail on the topic in this article: &lt;a href=&#34;/blog/2020/09/automated-testing-with-symfony/&#34;&gt;An introduction to automated testing for web applications with Symfony&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;writing-another-simple-test-case-that-stores-data&#34;&gt;Writing another simple test case that stores data&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s write another test that exercises &lt;code&gt;QuoteService&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetAllQuotes&lt;/code&gt; method. This time though, let&amp;rsquo;s add a new record to the database before calling it so that the method&amp;rsquo;s result is not empty. Here&amp;rsquo;s what the test looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Models&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Linq&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Tests.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteServiceTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; GetAllQuotesReturnsTheStoredData()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Given&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; dbContext = CreateDbContext();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; quote = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Quote
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                OfferedQuote = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Message = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;test_quote_message&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Year = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;2000&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Make = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Toyota&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Model = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Corolla&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                BodyTypeID = dbContext.BodyTypes.Single(bt =&amp;gt; bt.Name == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Sedan&amp;#34;&lt;/span&gt;).ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                SizeID = dbContext.Sizes.Single(s =&amp;gt; s.Name == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Compact&amp;#34;&lt;/span&gt;).ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ItMoves = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasAllWheels = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasAlloyWheels = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasAllTires = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasKey = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasTitle = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                RequiresPickup = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasEngine = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasTransmission = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HasCompleteInterior = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                CreatedAt = DateTime.Now
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            dbContext.Quotes.Add(quote);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            dbContext.SaveChanges();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; service = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; QuoteService(dbContext, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// When&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; service.GetAllQuotes();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// Then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.NotEmpty(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.Single(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.Equal(quote.ID, result.First().ID);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.Equal(quote.OfferedQuote, result.First().OfferedQuote);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Assert.Equal(quote.Message, result.First().Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First we include the &lt;code&gt;VehicleQuotes.Models&lt;/code&gt; namespace so that we can use the &lt;code&gt;Quotes&lt;/code&gt; model class. In our REST API, this is the class that represents the data from the &lt;code&gt;quotes&lt;/code&gt; table. This is the main table that &lt;code&gt;GetAllQuotes&lt;/code&gt; queries. We also include the &lt;code&gt;System.Linq&lt;/code&gt; namespace, which allows us to use various collection extension methods (like &lt;code&gt;Single&lt;/code&gt; and &lt;code&gt;First&lt;/code&gt;) which we leverage throughout the test case to query lookup tables and assert on the test results.&lt;/p&gt;
&lt;p&gt;Other than that, the test case itself is pretty self-explanatory. We start by obtaining an instance of &lt;code&gt;VehicleQuotesContext&lt;/code&gt; via the &lt;code&gt;CreateDbContext&lt;/code&gt; method. Remember that this also resets the whole database so that the test case can run over a clean slate. Then, we create a new &lt;code&gt;Quote&lt;/code&gt; object and use our &lt;code&gt;VehicleQuotesContext&lt;/code&gt; to insert it as a record into the database. We do this so that the later call to &lt;code&gt;QuoteService&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetAllQuotes&lt;/code&gt; method actually finds some data to return this time. Finally, the test case validates that the result contains a record and that its data is identical to what we set manually.&lt;/p&gt;
&lt;p&gt;Neat! At this point we have what I think is the bare minimum infrastructure when it comes to serviceable and effective database integration tests, namely, access to a test database. We can take it one step further, though, and make things more reusable and a little bit better performing.&lt;/p&gt;
&lt;h3 id=&#34;refactoring-into-a-fixture-for-reusability&#34;&gt;Refactoring into a fixture for reusability&lt;/h3&gt;
&lt;p&gt;We can use the test fixture functionality offered by xUnit.net in order to make the database interactivity aspect of our test suite into a reusable component. That way, if we had other test classes focused on other components that interact with the database, we could just plug that code in. We can define a fixture by creating a new file called, for example, &lt;code&gt;VehicleQuotes.Tests/Fixtures/DatabaseFixture.cs&lt;/code&gt; with these contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Hosting&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Configuration&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Tests.Fixtures&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;DatabaseFixture&lt;/span&gt; : IDisposable
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; VehicleQuotesContext DbContext { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DatabaseFixture()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            DbContext = CreateDbContext();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; Dispose()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            DbContext.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; VehicleQuotesContext CreateDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; host = Host.CreateDefaultBuilder().Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; config = host.Services.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; options = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; DbContextOptionsBuilder&amp;lt;VehicleQuotesContext&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .UseNpgsql(config.GetConnectionString(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;VehicleQuotesContext&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .Options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; context = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; VehicleQuotesContext(options);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            context.Database.EnsureDeleted();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            context.Database.EnsureCreated();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All this class does is define the &lt;code&gt;CreateDbContext&lt;/code&gt; method that we&amp;rsquo;re already familiar with but puts it in a nice reusable package. Upon instantiation, as seen in the constructor, it stores a reference to the &lt;code&gt;VehicleQuotesContext&lt;/code&gt; in its &lt;code&gt;DbContext&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;With that, our &lt;code&gt;QuoteServiceTests&lt;/code&gt; test class can use it if we make the following changes to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; using System;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; using Xunit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-using Microsoft.EntityFrameworkCore;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt; using VehicleQuotes.Services;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-using Microsoft.Extensions.Hosting;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-using Microsoft.Extensions.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-using Microsoft.Extensions.DependencyInjection;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt; using VehicleQuotes.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; using System.Linq;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+using VehicleQuotes.Tests.Fixtures;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; namespace VehicleQuotes.Tests.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    public class QuoteServiceTests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    public class QuoteServiceTests : IClassFixture&amp;lt;DatabaseFixture&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        private VehicleQuotesContext dbContext;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        public QuoteServiceTests(DatabaseFixture fixture)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            dbContext = fixture.DbContext;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-        private VehicleQuotesContext CreateDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-        {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var host = Host.CreateDefaultBuilder().Build();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var config = host.Services.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var options = new DbContextOptionsBuilder&amp;lt;VehicleQuotesContext&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-                .UseNpgsql(config.GetConnectionString(&amp;#34;VehicleQuotesContext&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-                .UseSnakeCaseNamingConvention()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-                .Options;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var context = new VehicleQuotesContext(options);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            context.Database.EnsureDeleted();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            context.Database.EnsureCreated();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            return context;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         [Fact]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         public async void GetAllQuotesReturnsEmptyWhenThereIsNoDataStored()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             // Given
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var dbContext = CreateDbContext();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         [Fact]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         public async void GetAllQuotesReturnsTheStoredData()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             // Given
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            var dbContext = CreateDbContext();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we&amp;rsquo;ve updated the &lt;code&gt;QuoteServiceTests&lt;/code&gt; class definition so that it inherits from &lt;a href=&#34;https://github.com/xunit/xunit/blob/main/src/xunit.v3.core/IClassFixture.cs&#34;&gt;&lt;code&gt;IClassFixture&amp;lt;DatabaseFixture&amp;gt;&lt;/code&gt;&lt;/a&gt;. This is how we tell xUnit.net that our tests use the new fixture that we created. Next, we define a constructor that receives a &lt;code&gt;DatabaseFixture&lt;/code&gt; object as a parameter. That&amp;rsquo;s how xUnit.net allows our test class to access the capabilities provided by the fixture. In this case, we take the fixture&amp;rsquo;s &lt;code&gt;DbContext&lt;/code&gt; instance, and store it for later use in all of the test cases that need database interaction. We also removed the &lt;code&gt;CreateDbContext&lt;/code&gt; method because now that&amp;rsquo;s defined within the fixture. We also removed a few &lt;code&gt;using&lt;/code&gt; statements that became unnecessary.&lt;/p&gt;
&lt;p&gt;One important aspect to note about this fixture is that it is initialized once per whole test suite run, not once per test case. Specifically, the code within the &lt;code&gt;DatabaseFixture&lt;/code&gt;&amp;rsquo;s constructor gets executed once, before all of the test cases. Similarly, the code in &lt;code&gt;DatabaseFixture&lt;/code&gt;&amp;rsquo;s &lt;code&gt;Dispose&lt;/code&gt; method get executed once at the end, after all test cases have been run.&lt;/p&gt;
&lt;p&gt;This means that our test database deletion and recreation step now happens only once for the entire test suite. This is not good with our current implementation because that means that individual test cases no longer run with a fresh, empty database. This can be good for performance though, as long as we update our test cases to run within database transactions. Let&amp;rsquo;s do just that.&lt;/p&gt;
&lt;h3 id=&#34;using-transactions-to-reset-the-state-of-the-database&#34;&gt;Using transactions to reset the state of the database&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s how we update out test class so that each test case runs within a transaction:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; namespace VehicleQuotes.Tests.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    public class QuoteServiceTests : IClassFixture&amp;lt;DatabaseFixture&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    public class QuoteServiceTests : IClassFixture&amp;lt;DatabaseFixture&amp;gt;, IDisposable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         private VehicleQuotesContext dbContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         public QuoteServiceTests(DatabaseFixture fixture)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             dbContext = fixture.DbContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            dbContext.Database.BeginTransaction();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;         }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        public void Dispose()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            dbContext.Database.RollbackTransaction();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         # ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first thing to note here is that we added a call to &lt;code&gt;BeginTransaction&lt;/code&gt; in the test class constructor. xUnit.net creates a new instance of the test class for each test case. This means that this constructor is run before each and every test case. We use that opportunity to begin a database transaction.&lt;/p&gt;
&lt;p&gt;The other interesting point is that we&amp;rsquo;ve updated the class to implement the &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose&#34;&gt;&lt;code&gt;IDisposable&lt;/code&gt; interface&amp;rsquo;s &lt;code&gt;Dispose&lt;/code&gt; method&lt;/a&gt;. xUnit.net will run this code after each test case, so we rollback the transaction.&lt;/p&gt;
&lt;p&gt;Put those two together and we&amp;rsquo;ve updated our test suite so that every test case runs within the context of its own database transaction. Try it out with &lt;code&gt;dotnet test&lt;/code&gt; and see what happens.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To learn more about database transactions with Entity Framework Core, you can look at &lt;a href=&#34;https://docs.microsoft.com/en-us/ef/core/saving/transactions&#34;&gt;the official docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can learn more about xUnit.net&amp;rsquo;s test class fixtures in &lt;a href=&#34;https://github.com/xunit/samples.xunit/tree/main/ClassFixtureExample&#34;&gt;the samples repository&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Alright, that&amp;rsquo;s all for now. It is great to see that implementing automated database integration tests is actually fairly straightforward using .NET, xUnit.net, and Entity Framework. Even if it isn&amp;rsquo;t quite as easy as it is in Rails, it is perfectly doable.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Forwarding Google Forms responses to an external API</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/11/forwarding-google-forms-responses-to-api/"/>
      <id>https://www.endpointdev.com/blog/2021/11/forwarding-google-forms-responses-to-api/</id>
      <published>2021-11-16T00:00:00+00:00</published>
      <author>
        <name>Afif Sohaili</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/banner.jpg&#34; alt=&#34;Sunrise over the Wasatch mountains&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Mira Jensen --&gt;
&lt;p&gt;Google Forms is a great form service that many people use for surveys, research, questionnaires, etc. It has an intuitive and flexible interface for building forms and is fairly easy to use for everyone. Once you get a response, you can view the results in the admin section of the form or in a Google Sheets document in which Google will automatically insert all your responses.&lt;/p&gt;
&lt;p&gt;However, you may need to do something else with the responses. For example, what if you want to have the response printed in your Slack channel or Discord server? Or what if you want to use the raw data to make more complex visualizations than Google Sheets is capable of?&lt;/p&gt;
&lt;h3 id=&#34;google-apps-script-to-the-rescue&#34;&gt;Google Apps Script to the rescue!&lt;/h3&gt;
&lt;p&gt;Google Apps Script is a development platform for building add-ons for Google products, such as Google Sheets, Google Docs, and Google Forms. You write your JavaScript in the code editor that Google provides for you, so there is nothing to install on your local machine to start developing. This code then gets executed on Google&amp;rsquo;s servers. These Google Apps Script projects can then be published as a Google Workspace add-on that others can use or shared within your organization.&lt;/p&gt;
&lt;p&gt;Even though Google Apps Script is basically just JavaScript, there are a few key differences from a Node.js project. For example, in a Node.js environment you could use the &lt;code&gt;https&lt;/code&gt; module to send an HTTP request to another external service (or you could grab a package like &lt;code&gt;axios&lt;/code&gt; or &lt;code&gt;node-fetch&lt;/code&gt; that can do the same thing). In Google Apps Script, however, you cannot use the &lt;code&gt;https&lt;/code&gt; module because it is not a Node.js project per se and does not provide you with the ability to install external NPM packages. Instead, there is a limited set of standard libraries that comes built-in within a Google Apps Script project.&lt;/p&gt;
&lt;h4 id=&#34;1-start-a-new-google-apps-script-project&#34;&gt;1. Start a new Google Apps Script project&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Head to &lt;a href=&#34;https://forms.google.com&#34;&gt;Google Forms&lt;/a&gt; and create a new form.&lt;/li&gt;
&lt;li&gt;Click the triple-dots icon at the top-right corner of the form and choose &amp;ldquo;Script Editor&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;You should now see the Google Apps Script editor. Great! Let&amp;rsquo;s change the project name to something more descriptive (e.g. &amp;ldquo;Forward to API&amp;rdquo;).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;2-add-triggers-to-the-script-for-initialization&#34;&gt;2. Add triggers to the script for initialization&lt;/h4&gt;
&lt;p&gt;Google Apps Script allows you to install triggers to the current form. The ones we are interested in right now are &lt;code&gt;onOpen&lt;/code&gt;, which runs when a user that has edit access opens the form, and &lt;code&gt;onInstall&lt;/code&gt;, which runs when a user installs the add-on in the Google Forms form. There are other events that you might be interested in listed &lt;a href=&#34;https://developers.google.com/apps-script/guides/triggers&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We will also need to provide a way for users to set the outgoing URL for each response. The easiest would be to hardcode it in the code, but we can make our add-on a little bit more configurable; let&amp;rsquo;s also create a sidebar that contains the configuration interface for the users to change the settings for our add-on, in this case the destination URL for the responses.&lt;/p&gt;
&lt;p&gt;Now, in the Google Apps Script code editor, paste the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * Adds a custom menu to the active form to show the add-on sidebar.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; onOpen(e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  FormApp.getUi()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .createAddonMenu()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .addItem(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Configure&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;showSidebar&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .addToUi();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * Runs when the add-on is installed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; onInstall(e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  onOpen(e);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * Opens a sidebar in the form containing the add-on&amp;#39;s user interface for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * configuring the notifications this add-on will produce.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; showSidebar() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; sidebarPage = HtmlService.createHtmlOutputFromFile(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sidebar&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .setTitle(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Your add-on configuration&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  FormApp.getUi().showSidebar(sidebarPage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Save settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Load settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First, we would like to have a menu item on the form to access the add-on. This is done through the  &lt;code&gt;createAddonMenu().addItem&lt;/code&gt; function within the &lt;code&gt;onOpen&lt;/code&gt; trigger. We also have the &lt;code&gt;onInstall&lt;/code&gt; trigger that does the same thing as what &lt;code&gt;onOpen&lt;/code&gt; is doing. With these, both installing the add-on for the first time and opening the form creates an add-on menu item called &amp;ldquo;Configure&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createAddonMenu().addItem&lt;/code&gt; accepts two arguments, a label (&lt;code&gt;Configure&lt;/code&gt;) and a function name to be executed when the item is selected (&lt;code&gt;showSidebar&lt;/code&gt;). In the code above, it will run the &lt;code&gt;showSidebar&lt;/code&gt; function. &lt;code&gt;showSidebar&lt;/code&gt; initializes the add-on&amp;rsquo;s view by loading and running the HTML file we specified in &lt;code&gt;createHtmlOutputFromFile&lt;/code&gt;. Since we are passing &lt;code&gt;sidebar&lt;/code&gt; to the function, it will automatically assume the filename &lt;code&gt;sidebar.html&lt;/code&gt; and load the file from our project.&lt;/p&gt;
&lt;h4 id=&#34;3-adding-the-configuration-sidebar&#34;&gt;3. Adding the configuration sidebar&lt;/h4&gt;
&lt;p&gt;In the previous step, we specified our menu item to show the sidebar. Now, let&amp;rsquo;s go ahead and create a new file called &lt;code&gt;sidebar.html&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On the Files panel, click the &lt;code&gt;+&lt;/code&gt; icon and choose &lt;code&gt;HTML&lt;/code&gt; in the dropdown menu.&lt;/li&gt;
&lt;li&gt;Name the file &lt;code&gt;sidebar.html&lt;/code&gt; when prompted. This is important as we have specified the name &lt;code&gt;sidebar&lt;/code&gt; to the &lt;code&gt;createHtmlOutputFromFile&lt;/code&gt; function within &lt;code&gt;showSidebar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open the HTML file and paste the following HTML content:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;base&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;target&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;_top&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;font-family&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sans-serif&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;border&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;solid&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;#3f3f3f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.25&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;rem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      #&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;margin-top&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;rem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input-field&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;margin-bottom&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;rem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;form&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;input-field&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;for&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&amp;gt;URL to send responses to:&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;label&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;placeholder&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;e.g. https://some-api.com/accept/responses&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;block&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;button-bar&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;action&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;save-settings&amp;#34;&lt;/span&gt;&amp;gt;Save&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;response&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;form&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;src&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;//ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Our JavaScript code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h6 id=&#34;test-your-sidebar&#34;&gt;Test your sidebar&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;Go back to your Google Forms form and refresh. You should now get an add-on menu on your form, with your add-on listed in the dropdown.&lt;/li&gt;
&lt;li&gt;Choose your add-on and click &amp;ldquo;Configure&amp;rdquo;. This is the add-on menu item that we declared in step 1.&lt;/li&gt;
&lt;li&gt;You should now be prompted to authorize the script. Follow the instructions to allow the script permission to run on your Google Forms forms.&lt;/li&gt;
&lt;li&gt;Once you&amp;rsquo;re done, go to the add-on menu, choose your add-on, and choose &amp;ldquo;Configure&amp;rdquo; again. You should now see a sidebar on the right side of the form with a text field to fill in the destination URL and a submit button to save the settings.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/forms-authorization.png&#34; alt=&#34;Authorization&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/sidebar-1.png&#34; alt=&#34;Your sidebar&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;4-saving-your-configuration&#34;&gt;4. Saving your configuration&lt;/h4&gt;
&lt;p&gt;Now we need to be able to save the URL we want to send the responses to and create a form trigger out of that. Let&amp;rsquo;s go ahead and add that:&lt;/p&gt;
&lt;p&gt;Add the following script in the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block under the &lt;code&gt;// Our JavaScript code&lt;/code&gt; comment.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// sidebar.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;$(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// Load settings from the server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  loadSettingsAndPopulateForm();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// Listen to &amp;#39;submit&amp;#39; event
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;form&amp;#39;&lt;/span&gt;).submit(saveSettingsToServer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; loadSettingsAndPopulateForm() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  google.script.run
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .withSuccessHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(settings) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#url&amp;#39;&lt;/span&gt;).val(settings.url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .withFailureHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(msg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#response&amp;#39;&lt;/span&gt;).text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Failed to fetch settings. ERROR: &amp;#39;&lt;/span&gt; + msg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .fetchSettings();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; saveSettingsToServer(event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  event.preventDefault();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; button = $(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;).find(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;button&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  button.attr(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;disabled&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;disabled&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; settings = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;: $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#url&amp;#39;&lt;/span&gt;).val(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// Save the settings on the server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  google.script.run
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .withSuccessHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(msg, button) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          button.removeAttr(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;disabled&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#response&amp;#39;&lt;/span&gt;).text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Saved settings successfully&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .withFailureHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(msg, button) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          button.removeAttr(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;disabled&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#response&amp;#39;&lt;/span&gt;).text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Failed to save settings. ERROR: &amp;#39;&lt;/span&gt; + msg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .withUserObject(button)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .saveSettings(settings);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Code.gs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * Used by the client-side via `google.script.run` to save setings from the form.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; saveSettings(settings) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  PropertiesService.getDocumentProperties().setProperties(settings);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// adjustFormSubmitTrigger();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; * Used by the client-side via `google.script.run` to load saved setings.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; fetchSettings() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; PropertiesService.getDocumentProperties().getProperties();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, let&amp;rsquo;s go through the code:&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;$(function() {})&lt;/code&gt; block is run at document load. Two things happen here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;loadSettingsAndPopulateForm&lt;/code&gt; function runs the &lt;code&gt;fetchSettings&lt;/code&gt; function from the backend. Then it populates the text input field &lt;code&gt;#url&lt;/code&gt; with the saved settings.&lt;/li&gt;
&lt;li&gt;It installs an event listener on the configuration form submit to save the settings to the server. This listener, the &lt;code&gt;saveSettingsToServer&lt;/code&gt; function, gathers all the values from the input fields, and runs the &lt;code&gt;saveSettings&lt;/code&gt; function on the backend, with the input field values as the arguments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&#34;googlescriptrun&#34;&gt;google.script.run&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;google.script.run&lt;/code&gt; is the glue between frontend (the &lt;code&gt;*.html&lt;/code&gt; files) and the backend (the &lt;code&gt;*.gs&lt;/code&gt;) files. Once you declare a function (e.g. &lt;code&gt;doStuff&lt;/code&gt;) on backend, you can use the function through &lt;code&gt;google.script.run.doStuff&lt;/code&gt; from the frontend. &lt;code&gt;withSuccessHandler&lt;/code&gt; and &lt;code&gt;withFailureHandler&lt;/code&gt; are where you supply the callbacks for successful calls and failed calls to the backend respectively.&lt;/p&gt;
&lt;p&gt;To see more, read the &lt;a href=&#34;https://developers.google.com/apps-script/guides/html/reference/run&#34;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;5-installing-the-form-submit-event-handler&#34;&gt;5. Installing the form submit event handler&lt;/h4&gt;
&lt;p&gt;Now that we&amp;rsquo;re able to pinpoint the address we want send the form data to, we can start instructing Google Forms to send the data our way when someone submits the form. In order to do that, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Code the form submit trigger to submit the data on the form response.&lt;/li&gt;
&lt;li&gt;If there&amp;rsquo;s no existing form submit trigger and the URL is set, install the form submit trigger.&lt;/li&gt;
&lt;li&gt;If there is an existing form submit trigger and the URL is unset, remove the trigger.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s write the form submit trigger first. Paste this code at the end of &lt;code&gt;Code.gs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; sendResponse(e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; data = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: e.source.getId(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: e.source.getTitle() ? e.source.getTitle() : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Untitled Form&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;is_private&amp;#34;&lt;/span&gt;: e.source.requiresLogin(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;is_published&amp;#34;&lt;/span&gt;: e.source.isAcceptingResponses(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;response&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: e.response.getId(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt;: e.response.getRespondentEmail(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: e.response.getTimestamp(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: e.response.getItemResponses().map(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(y) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          h: y.getItem().getTitle(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          k: y.getResponse()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;).reduce(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(r, y) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        r[y.h] = y.k;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }, {}),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; options = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    method: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payload: JSON.stringify(data),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    contentType: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;application/json; charset=utf-8&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; settings = PropertiesService.getDocumentProperties();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UrlFetchApp.fetch(settings.getProperty(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;), options);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s break it down. First, we compile the data that we want to send to our application. Form triggers accept a variable in their parameter, which we named &lt;code&gt;e&lt;/code&gt; here. &lt;code&gt;e&lt;/code&gt; is an &lt;a href=&#34;https://developers.google.com/apps-script/guides/triggers/events?hl=en&#34;&gt;Event Object&lt;/a&gt;, and it provides us access to the form&amp;rsquo;s information (via &lt;code&gt;e.source&lt;/code&gt;) as well as the responses to the form (via &lt;code&gt;e.response&lt;/code&gt;). We gather all this in an object (called &lt;code&gt;data&lt;/code&gt; in this case) and submit it to our app via the &lt;code&gt;UrlFetchApp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app&#34;&gt;UrlFetchApp&lt;/a&gt; is a standard library in Google Apps Script. It allows us to make HTTP requests to other applications. Here, we are doing a &lt;code&gt;POST&lt;/code&gt; HTTP request to the URL that we set (obtained via &lt;code&gt;PropertiesService.getDocumentProperties.getProperty(&#39;url&#39;)&lt;/code&gt;).&lt;/p&gt;
&lt;h5 id=&#34;adjusting-the-form-trigger&#34;&gt;Adjusting the form trigger&lt;/h5&gt;
&lt;p&gt;Right now, &lt;code&gt;sendResponse&lt;/code&gt; is not hooked to any function. Let&amp;rsquo;s hook it to the form trigger with this code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; adjustFormSubmitTrigger() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; form = FormApp.getActiveForm();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; triggers = ScriptApp.getUserTriggers(form);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; settings = PropertiesService.getDocumentProperties();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; url = settings.getProperty(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; triggerNeeded = url &amp;amp;&amp;amp; url.length &amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// Create a new trigger if required; delete existing trigger
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#888&#34;&gt;//   if it is not needed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; existingTrigger = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; i = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;; i &amp;lt; triggers.length; i++) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (triggers[i].getEventType() == ScriptApp.EventType.ON_FORM_SUBMIT) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      existingTrigger = triggers[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (triggerNeeded &amp;amp;&amp;amp; !existingTrigger) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; trigger = ScriptApp.newTrigger(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sendResponse&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .forForm(form)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .onFormSubmit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .create();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!triggerNeeded &amp;amp;&amp;amp; existingTrigger) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ScriptApp.deleteTrigger(existingTrigger);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don&amp;rsquo;t forget to also uncomment &lt;code&gt;// adjustFormSubmitTrigger&lt;/code&gt; in the &lt;code&gt;saveSettings&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;This function is pretty straightforward. It checks if there is a URL saved in the settings and installs the form submit trigger if needed. If there is no URL saved or if it&amp;rsquo;s an empty string, then any existing form submit trigger will be deleted. Changing the URL will not do anything to the existing form submit trigger, since &lt;code&gt;sendResponse&lt;/code&gt; will always pull the latest URL from the settings.&lt;/p&gt;
&lt;h4 id=&#34;6-test-the-triggers&#34;&gt;6. Test the triggers&lt;/h4&gt;
&lt;p&gt;Now we can test the submission. Let&amp;rsquo;s configure our add-on with a valid URL to our app, add a couple of questions to our form, then hit &lt;code&gt;Preview&lt;/code&gt; in the top navigation bar to test our form submit trigger.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/forms-question.png&#34; alt=&#34;Form&#34;&gt;&lt;/p&gt;
&lt;p&gt;Once we&amp;rsquo;ve filled the form, hit &lt;code&gt;Submit&lt;/code&gt;. You should now see your form response gets sent to your destination URL as a POST request.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/11/forwarding-google-forms-responses-to-api/forms-sample-response.png&#34; alt=&#34;Form submit&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I get from a simple Express app I developed to receive the response:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Example app listening at http://localhost:4000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;1zm_anJLsYnnmI-MlRqLcEhK8Eemd90rg_oJmsThiONw&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;HR Benefits Survey&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;is_private&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;is_published&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;response&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;2_ABaOnufmy5dDzYsUl3-G5vlkQaOPoW-Jf5Wskk9SZHhXvpgnTzx6LGVq9YGDqivvo6TXpko&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;2021-11-07T08:56:08.299Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;What&amp;#39;s the most important thing for you?&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;4-day workweek&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Select games you&amp;#39;d like to have at the office&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Foosball&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Dart&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s it! We can now send Google Forms responses to our app for better data processing and visualization.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Salesforce Integration with Node.js</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/03/salesforce-integration-with-node/"/>
      <id>https://www.endpointdev.com/blog/2020/03/salesforce-integration-with-node/</id>
      <published>2020-03-27T00:00:00+00:00</published>
      <author>
        <name>Dylan Wooters</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/03/salesforce-integration-with-node/fulton-ceiling.jpg&#34; alt=&#34;Patterned roof&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Photo by Dylan Wooters, 2020&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Salesforce is huge. It is currently the dominant customer relationship management (CRM) provider, accounting for &lt;a href=&#34;https://www.forbes.com/sites/louiscolumbus/2019/06/22/salesforce-now-has-over-19-of-the-crm-market/#75a6cdf9333a&#34;&gt;around 20%&lt;/a&gt; of market share. Businesses are using Salesforce not only as a traditional CRM solution, but also for novel purposes. Salesforce can serve as a backend database and admin portal for custom apps, or as a reporting tool that pulls data from various systems.&lt;/p&gt;
&lt;p&gt;This growth leads to increasing demand for Salesforce integrations. The term “Salesforce integration” may conjure up images of expensive enterprise software or dense API documentation, but it doesn’t have to be that way. You can work with Salesforce easily using Node.js and the npm package &lt;a href=&#34;https://jsforce.github.io/&#34;&gt;JSforce&lt;/a&gt;. An example of a project that might benefit from this kind of Node.js integration is an e-commerce website where order data is loaded to and from Salesforce for order fulfillment, tracking, and reporting.&lt;/p&gt;
&lt;p&gt;In this post we’ll cover how to connect to Salesforce using JSforce, the basics of reading and writing data, as well as some advanced topics like working with large amounts of data and streaming data with &lt;a href=&#34;https://socket.io/&#34;&gt;Socket.IO&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;setting-up&#34;&gt;Setting Up&lt;/h3&gt;
&lt;p&gt;You’ll first want to &lt;a href=&#34;https://nodejs.org/en/download/&#34;&gt;install Node.js&lt;/a&gt; on your local machine, if you haven’t done so already.&lt;/p&gt;
&lt;p&gt;Next, create your Node app. This will vary with your requirements. I often use &lt;a href=&#34;https://expressjs.com/&#34;&gt;Express&lt;/a&gt; to build a REST API for integration purposes. Other times, if I am routinely loading data into Salesforce, I will create Node scripts and schedule them using cron. For the purposes of this post, we will create a small Node script that can be run on the command line.&lt;/p&gt;
&lt;p&gt;Create a new directory for your project, and within that directory, run &lt;code&gt;npm init&lt;/code&gt; to generate your package.json file. Then install JSforce with &lt;code&gt;npm install jsforce&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, create a file named script.js, which we will run on the command line for testing. To test the script at any time, simply navigate to your app’s directory and run &lt;code&gt;node script.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At the top of the script, require &lt;code&gt;jsforce&lt;/code&gt;, as well as the Node IO libraries &lt;code&gt;fs&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt;. Then define an asynchronous function that will serve as our script body. This is where all of your Salesforce code will go.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; jsforce = require(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;jsforce&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; fs = require(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; path = require(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;run();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; run() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// salesforce code goes here...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;connecting-to-salesforce&#34;&gt;Connecting to Salesforce&lt;/h3&gt;
&lt;p&gt;I usually store my Salesforce credentials and instance URL as a JSON object in a separate file, which I gitignore. This ensures that sensitive data does not appear in Git. Below is the content of my salesforce-creds.json file. You’ll want to add your Salesforce username and password and update the instance URL, if necessary.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;your&lt;/span&gt; &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;username&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;your&lt;/span&gt; &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;password&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://na111.salesforce.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To connect to Salesforce simply retrieve the credentials from the file and use them with the JSforce Connection class to login. Be sure to wrap all JSforce code in a try-catch block, to catch any errors coming back from Salesforce.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; creds = JSON.parse(fs.readFileSync(path.resolve(__dirname, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;./salesforce-creds.json&amp;#39;&lt;/span&gt;)).toString());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; conn = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; jsforce.Connection({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loginUrl: creds.url
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.login(creds.username, creds.password);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Connected to Salesforce!&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// now you can use conn to read/write data...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.logout();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;catch&lt;/span&gt; (err) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.error(err);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;reading-writing-and-deleting-data&#34;&gt;Reading, Writing, and Deleting Data&lt;/h3&gt;
&lt;p&gt;Once connected, the easiest way to query data from Salesforce is to use the JSforce query function, and pass in an &lt;a href=&#34;https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm&#34;&gt;SOQL&lt;/a&gt; statement. This offers the most flexibility, as you can run queries for child and parent objects. Using SOQL, we can query all accounts and their contacts (children) in a single statement. Note, however, that there are &lt;a href=&#34;https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_query_limits.htm&#34;&gt;limitations on relationship queries&lt;/a&gt;. You can only go down one level, from parent to child, but you can go up multiple levels from child to parent.&lt;/p&gt;
&lt;p&gt;Writing and deleting data is simple with JSforce using the &lt;code&gt;sobject&lt;/code&gt; class and the corresponding create/update/delete function. In the example below, we will query for accounts and contacts using SOQL, and then isolate and update a specific contact using &lt;code&gt;sobject().update&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; soql = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`select id, name,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;    (SELECT Id, FirstName, LastName, Email_Verified__c, Enrollment_Status__c from Contacts)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;    FROM Account`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; accounts = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.query(soql);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; cooper = accounts.records
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .filter(x =&amp;gt; x.Name === &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Twin Peaks Sheriff Dept.&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;].Contacts.records
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .filter(y =&amp;gt; y.FirstName === &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Dale&amp;#39;&lt;/span&gt; &amp;amp;&amp;amp; y.LastName === &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Cooper&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;console.log(cooper);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Console output:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     attributes: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//         type: &amp;#39;Contact&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//         url: &amp;#39;/services/data/v42.0/sobjects/Contact/0033h000001sDzDAAU&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     Id: &amp;#39;0033h000001sDzDAAU&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     FirstName: &amp;#39;Dale&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     LastName: &amp;#39;Cooper&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     Email_Verified__c: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     Enrollment_Status__c: &amp;#39;Pending&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cooper.Enrollment_Status__c = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Accepted&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; ret = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.sobject(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Contact&amp;#39;&lt;/span&gt;).update(cooper);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (ret.success) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Contact updated in Salesforce.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;working-with-large-amounts-of-data&#34;&gt;Working with Large Amounts of Data&lt;/h3&gt;
&lt;p&gt;You may need to read and write large amounts of data, for example if you are using Salesforce for reporting and loading data to and from other systems.&lt;/p&gt;
&lt;h4 id=&#34;event-driven-querying&#34;&gt;Event-driven Querying&lt;/h4&gt;
&lt;p&gt;The record limit for standard promise-style SOQL querying, as in our example above, is 2000 records. To query more than that, it is best to shift to the event-driven style of querying. This will ensure that all records are successfully retrieved from Salesforce. You can use the &lt;code&gt;maxFetch&lt;/code&gt; property to set the upper limit of records returned. By default, &lt;code&gt;maxFetch&lt;/code&gt; is set to 10,000.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; contacts = [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; soql = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;SELECT Id, FirstName, LastName, Email_Verified__c, Enrollment_Status__c from Contact&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; query = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.query(soql)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .on(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;record&amp;#34;&lt;/span&gt;, (record) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        contacts.push(record);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .on(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;end&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`Fetched Contacts. Total records fetched: &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;contacts.length&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .on(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, (err) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.error(err);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .run({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        autoFetch: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        maxFetch: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;5000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;loading-data-with-the-bulk-api&#34;&gt;Loading Data with the Bulk API&lt;/h4&gt;
&lt;p&gt;Loading a large amount of data into Salesforce is best accomplished through the &lt;a href=&#34;https://jsforce.github.io/document/#bulk-api&#34;&gt;Bulk API&lt;/a&gt; via JSforce. There are a couple good reasons for this approach.&lt;/p&gt;
&lt;p&gt;The Bulk API has better performance over other methods when working with large collections of objects.&lt;/p&gt;
&lt;p&gt;The standard JSforce &lt;code&gt;sobject&lt;/code&gt; create/update/delete functions have a 200 object limit. For operations on large collections, you must divide the total by 200, resulting in many separate API calls. By contrast, the Bulk API only uses a single API call. Since Salesforce imposes &lt;a href=&#34;https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_api.htm&#34;&gt;API limits&lt;/a&gt;, this makes the Bulk API a better choice.&lt;/p&gt;
&lt;p&gt;Running a bulk operation is simple using the &lt;code&gt;bulk.load&lt;/code&gt; method, which takes three parameters: the Salesforce object type, the operation type, and an array of objects. The method returns an array of objects with success/errors fields, as well as the id of the newly created object, if successful.&lt;/p&gt;
&lt;p&gt;If you’re working with thousands of objects, it’s good to set the pollTimeout property manually to one minute or more, to avoid Salesforce connection timeouts. Also note that the possible values for operation type are: ‘insert’, ‘update’, ‘upsert’, ‘delete’, or ‘hardDelete’.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// set poll timeout to one minute for larger datasets
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;sfConnection.bulk.pollTimeout = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;240000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// normally you will have thousands of Accounts, this is just an example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; accounts = [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Saul Goodman, LLC&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Los Pollos Hermanos Inc&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Hamlin, Hamlin &amp;amp; McGill&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; results = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.bulk.load(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Account&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;insert&amp;#39;&lt;/span&gt;, accounts);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;console.log(results);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Console output:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     { id: &amp;#39;0013h000006bdd2AAA&amp;#39;, success: true, errors: [] },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     { id: &amp;#39;0013h000006bdd3AAA&amp;#39;, success: true, errors: [] },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//     { id: &amp;#39;0013h000006bdd4AAA&amp;#39;, success: true, errors: [] }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (accounts.length === results.filter(x =&amp;gt; x.success).length) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;All account successfully loaded.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;websocket-streaming-with-socketio&#34;&gt;WebSocket Streaming with Socket.io&lt;/h3&gt;
&lt;p&gt;Say you are building a web application for reporting, and the app contains a dashboard with data on all of your contacts in Salesforce. You want the dashboard to be updated whenever the data in Salesforce changes, and you also want this to happen without refreshing the web page.&lt;/p&gt;
&lt;p&gt;To accomplish this, you can stream real-time data from Salesforce using JSforce and the &lt;a href=&#34;https://socket.io/&#34;&gt;Socket.IO library&lt;/a&gt;, which makes working with WebSockets quite simple.&lt;/p&gt;
&lt;p&gt;The first step in this process is creating a &lt;a href=&#34;https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/code_sample_java_create_pushtopic.htm&#34;&gt;PushTopic&lt;/a&gt; in Salesforce. This is basically a trigger that emits a notification anytime an object is created, updated, etc. in Salesforce. I created a PushTopic for Contacts by running the following Apex code in the Salesforce developer console.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PushTopic pushTopic = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; PushTopic();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;UserChange&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.Query = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;SELECT Id, FirstName, LastName, Email_Verified__c, Enrollment_Status__c FROM Contact&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.ApiVersion = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;48.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.NotifyForOperationCreate = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.NotifyForOperationUpdate = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.NotifyForOperationUndelete = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.NotifyForOperationDelete = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pushTopic.NotifyForFields = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Referenced&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;insert pushTopic;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, back in your Node app, install Express and Socket.IO.&lt;/p&gt;
&lt;p&gt;Next, you’ll want to create a very basic Express server that will listen for updates from the Salesforce PushTopic, and emit them to your reporting site. Start by installing Express and Socket.IO.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install express
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install socket.io&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then delete the &lt;code&gt;run&lt;/code&gt; function in your script.js file, which contained the code from the samples above, and replace it with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; run() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// listen with express
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    server.listen(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;3000&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;listening on *:3000&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// connect to Salesforce
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; creds = JSON.parse(fs.readFileSync(path.resolve(__dirname, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;./salesforce-creds.json&amp;#39;&lt;/span&gt;)).toString());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; conn = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; jsforce.Connection({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        loginUrl: creds.url
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; conn.login(creds.username, creds.password);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;catch&lt;/span&gt; (err) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.error(err);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// when the client connects, emit streaming updates from salesforce to client
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    io.on(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;connection&amp;#34;&lt;/span&gt;, (socket) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;A socket connection was made!&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;let&lt;/span&gt; eventHandler = (message) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            console.log(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;New streaming event received from Salesforce:&amp;#39;&lt;/span&gt;, message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            socket.emit(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;UserChange&amp;#39;&lt;/span&gt;, message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        conn.streaming.topic(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;UserChange&amp;#39;&lt;/span&gt;).subscribe(eventHandler);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is a step-by-step description of what is occurring in the code sample above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Express server is set to listen for connections on port 3000.&lt;/li&gt;
&lt;li&gt;We connect to Salesforce and login.&lt;/li&gt;
&lt;li&gt;Socket.IO is set to listen for incoming connections from clients.&lt;/li&gt;
&lt;li&gt;A function called &lt;code&gt;eventHandler&lt;/code&gt; that emits Salesforce streaming messages to the client is defined.&lt;/li&gt;
&lt;li&gt;When a connection is made, &lt;code&gt;eventHandler&lt;/code&gt; is attached to the Salesforce streaming topic as a callback, using the live Salesforce connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you follow the nice little &lt;a href=&#34;https://socket.io/get-started/chat/&#34;&gt;tutorial from Socket.IO&lt;/a&gt; and create the sample chat webpage, you can actually test the Salesforce streaming updates. In the chat page, add this script, which will log messages coming back from Salesforce.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; socket = io();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    socket.on(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;UserChange&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(msg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        console.log(msg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then update a contact in Salesforce, changing the contact’s first name. If everything works correctly, you should see the client connect via Socket.IO in the Node logs, and also see a streaming message from Salesforce logged in the browser’s console window.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Node.js and JSforce provide a straightforward and elegant way to interact with Salesforce. Whether you have an existing Node API that needs to work with Salesforce, or you are building a new application that is powered by Salesforce data, consider the recipes above as stepping stones for completing your project.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Data Center Relocation Initiatives: Daunting But Achievable</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/"/>
      <id>https://www.endpointdev.com/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/</id>
      <published>2018-12-20T00:00:00+00:00</published>
      <author>
        <name>Charles Chang</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/banner.jpg&#34; alt=&#34;Datacenter room&#34;&gt;
&lt;a href=&#34;https://www.flickr.com/photos/seeweb/9771315606/&#34;&gt;Photo by Seeweb · CC BY-SA 2.0, cropped&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;A customer asked for our help dealing with logistical nightmares they encountered during a hardware update and data center relocation project. The customer had two active data centers, and wanted to relocate one of them to a modern Tier 4 facility to improve its performance and provide redundancy for critical systems. They also wanted to consolidate or decommission equipment to reduce recurring expenses and reduce their carbon footprint. The client decided to move to a Tier 4 data center because they provide across-​the-​board redundancy within the data center.&lt;/p&gt;
&lt;h3 id=&#34;moving-forward-with-tier-iv&#34;&gt;Moving forward with Tier IV&lt;/h3&gt;
&lt;p&gt;
&lt;img align=&#34;right&#34; src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/data-center-1.jpg&#34; style=&#34;margin: 1em&#34; width=&#34;200&#34;&gt;
Enterprises typically transition to Tier 4 data centers because they offer the highest uptime guarantees, and have no single points of failure. These facilities are fully redundant in terms of electrical circuits, cooling, and networking. This architecture is best able to withstand even the most serious technical incidents without server availability being affected. Tier IV facilities have contracts with disaster management companies who will provide them, for example, with fuel in the event that a natural disaster damages the power grid.
&lt;/p&gt;
&lt;p&gt;(More information about the four data center levels is at the end of this post.)&lt;/p&gt;
&lt;h3 id=&#34;key-issues&#34;&gt;Key Issues&lt;/h3&gt;
&lt;p&gt;Some of the customer’s key concerns were:&lt;/p&gt;
&lt;p&gt;1. To safely move their virtual environment to the new data center.&lt;/p&gt;
&lt;p&gt;2. To protect assets during the relocation.&lt;/p&gt;
&lt;p&gt;3. To completely shut down the current data center and migrate seamlessly to the new data center.&lt;/p&gt;
&lt;p&gt;4. To update external DNS records without causing any downtime for the web applications used by their customers.&lt;/p&gt;
&lt;h3 id=&#34;project-planning&#34;&gt;Project Planning&lt;/h3&gt;
&lt;p&gt;To manage data center relocations is challenging because there are many moving parts and variables. Typically, planning starts 6 months ahead of the scheduled relocation date. We seek to understand the role of each system by consulting in-depth with all the teams involved. We analyze each part of the client’s tech infrastructure, break it down, and strategize about the best approach for handling the migration of each component. Additional time to plan can help especially when a large group of stakeholders is involved. Successful planning requires hard and sometimes extensive work, but makes an otherwise overwhelming data center migration smooth and worry free.&lt;/p&gt;
&lt;p&gt;Here are some of our procedures for pre-​migration preparation:&lt;/p&gt;
&lt;p&gt;1. Process the paperwork for new hardware. Identify, review, and approve all applications. Hire movers. Schedule and coordinate with internal management and key stakeholders like IT staff, system admins, contractors, circuit providers, etc.&lt;img align=&#34;right&#34; src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/data-center-2.jpg&#34; style=&#34;margin: 1em&#34; width=&#34;200&#34;/&gt;&lt;/p&gt;
&lt;p&gt;2. Collect and analyze resource usage associated with the critical and non-critical systems and compare resource needs to resource availability at the new facility.&lt;/p&gt;
&lt;p&gt;3. Design and schedule the migration of critical systems to the new data center. Discuss the plan with key stakeholders.&lt;/p&gt;
&lt;p&gt;4. Make sure replicas of critical systems are up to date.&lt;/p&gt;
&lt;p&gt;5. Set up a new environment with new hardware in the new data center and establish network connections.&lt;/p&gt;
&lt;p&gt;6. Prepare hardware and virtualization technology in the new data center.&lt;/p&gt;
&lt;p&gt;7. Test, retest, and burn in the new hardware (2–4 weeks of testing).&lt;/p&gt;
&lt;p&gt;8. Create a non-​essential server within the virtualization environment and test it.&lt;/p&gt;
&lt;h3 id=&#34;a-challenge-eliminating-downtime&#34;&gt;A Challenge: Eliminating Downtime&lt;/h3&gt;
&lt;img align=&#34;right&#34; src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/data-center-3.jpg&#34; style=&#34;margin: 1em&#34; width=&#34;200&#34;/&gt;
&lt;p&gt;To minimize or eliminate downtime requires very careful preparation. With this particular client, we had the luxury of their already having a secondary data center, which could accommodate a majority of the critical systems during the migration.&lt;/p&gt;
&lt;p&gt;We originally planned to leverage this by simply changing the external DNS records to point to this data center during the migration, but during planning determined that the external DNS record would not replicate worldwide quickly enough to meet the uptime needs of their global operation.&lt;/p&gt;
&lt;h3 id=&#34;solutions&#34;&gt;Solutions&lt;/h3&gt;
&lt;p&gt;After discussing various approaches, we ended up using a load balancer. A load balancer is a network device that routes incoming traffic destined for a single destination (web site, application, or service) and ‘shares’ the incoming connections across multiple destination devices or services. In this case, the data center we were to migrate to housed the client’s critical systems like web servers, database, etc. The secondary data center housed a replica of the critical systems using a replication technology, e.g. Actifio, Rubrik, SAN mirroring, VMWare SRM, or Zerto — we used Actifio in this scenario.
&lt;img align=&#34;right&#34; src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/data-center-4.jpg&#34; style=&#34;margin: 1em&#34; width=&#34;200&#34;/&gt;&lt;/p&gt;
&lt;p&gt;The customer had multiple systems with external DNS records and public IP addresses. We did not change the public DNS names for the critical systems. We did change the public IP addresses associated with the public DNS names, to point to the load balancer, which then pointed to the appropriate data center throughout the process. This setup enabled a gradual, flexible, and fully controlled transition to the new data center.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;img align=&#34;right&#34; src=&#34;/blog/2018/12/data_center_relocation_initiatives_daunting_but_achievable/data-center-5.jpg&#34; style=&#34;margin: 1em&#34; width=&#34;200&#34;/&gt;
&lt;p&gt;Our client wanted to reduce their servers’ power usage 40% by consolidating into a hyper-​converged system. Other goals were to reduce floor space by 50%, and monthly recurring data center charges by 50%. We transitioned them to a new hardware stack (compute, storage, network, backup environment) in a Tier 4 data center, and saved them 50% percent on data center operation costs. Additionally, during the process we corrected issues associated with disaster recovery and business continuity, leaving the client’s operations more resilient, affordable, and more secure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;appendix-data-center-classifications&#34;&gt;Appendix: Data Center Classifications&lt;/h3&gt;
&lt;h4 id=&#34;tier-iv-best&#34;&gt;Tier IV (Best)&lt;/h4&gt;
&lt;p&gt;Tier IV data centers have redundancies for every process and data protection stream. No single outage or error can shut down the system. To keep Tier IV ranking, data centers must have 99.995% minimum uptime per year. They come equipped with 2N+1 infrastructure (two times the amount required for operation plus a backup), which is considered “fully redundant.”&lt;/p&gt;
&lt;h4 id=&#34;tier-iii-average&#34;&gt;Tier III (Average)&lt;/h4&gt;
&lt;p&gt;N+1 (the amount required for operation plus a backup) fault tolerance, which means a Tier III facility can undergo routine maintenance without a hiccup in operations. 99.982% minimum uptime per annum, used for maintenance and overwhelming emergencies.&lt;/p&gt;
&lt;h4 id=&#34;tier-ii-minimal&#34;&gt;Tier II (Minimal)&lt;/h4&gt;
&lt;p&gt;99.749% minimum uptime. Tier II facilities have considerably more downtime than Tier III facilities, primarily because Tier II facilities have less redundancy. They do however have partial cooling redundancy and multiple power redundancies.&lt;/p&gt;
&lt;h4 id=&#34;tier-i-basic-cheapest&#34;&gt;Tier I (Basic, Cheapest)&lt;/h4&gt;
&lt;p&gt;99.671% minimum uptime, with 28.8 hours of downtime each year.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Meet the End Point Windows consulting group</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/07/meet-the-end-point-windows-consulting-group/"/>
      <id>https://www.endpointdev.com/blog/2018/07/meet-the-end-point-windows-consulting-group/</id>
      <published>2018-07-27T00:00:00+00:00</published>
      <author>
        <name>Chris Hopkins</name>
      </author>
      <content type="html">
        &lt;p&gt;As the share of internet traffic passing through mobile devices hovers around 50%, it’s easy to forget that a lot of computing still happens on desktops and laptops—​and perhaps even easier to forget that the majority of those desktops and laptops are running Microsoft Windows.&lt;/p&gt;
&lt;p&gt;At End Point, we take pride in being a multi-platform organization and using open source technologies in real-world business environments. Our internal chat service, wiki, and many other libraries and tools we employ across our client base are all open source. However, for various reasons many companies choose Microsoft’s solutions including long-time standards Windows Server, Active Directory, and Exchange.&lt;/p&gt;
&lt;p&gt;In April 2017, &lt;strong&gt;Dan Briones&lt;/strong&gt; came to End Point to head up the company’s new Windows infrastructure services. In 2018 we added two new engineers to the mix, &lt;strong&gt;Chris Hopkins&lt;/strong&gt; and &lt;strong&gt;Charles Chang&lt;/strong&gt;, to form a full-service Windows consulting team, all local to our New York City office on Park Avenue.&lt;/p&gt;
&lt;h3 id=&#34;dan-briones&#34;&gt;Dan Briones&lt;/h3&gt;
&lt;img style=&#34;width: 300px&#34; alt=&#34;Dan Briones&#34; src=&#34;/blog/2018/07/meet-the-end-point-windows-consulting-group/dan_briones.jpg&#34; /&gt;
&lt;p&gt;Dan is the team lead and has over three decades of hands-on experience in IT systems management, systems integrations, migrations, virtualization, networking, security, orchestration, security, compliance, and maintenance, specializing in the Microsoft Windows ecosystem. He also develops applications using the .NET framework and SQL Server database. Dan works with many of our local New York clients, focusing on their network and infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;charles-chang&#34;&gt;Charles Chang&lt;/h3&gt;
&lt;img style=&#34;width: 300px&#34; alt=&#34;Charles Chang&#34; src=&#34;/blog/2018/07/meet-the-end-point-windows-consulting-group/charles_chang.jpg&#34; /&gt;
&lt;p&gt;Charles has 21 years of experience in IT ranging from managing Windows infrastructure, to datacenter relocation and hardware refresh initiatives, office relocation, virtualization, disaster recovery and business continuity strategy. Charles has worked extensively with major vendors such as VMware, IBM, EMC, Cisco, and Dell in building enterprise environments in the datacenter. He also has experience in implementing data loss prevention technology, web and email filtering to protect and provide a healthy infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;chris-hopkins&#34;&gt;Chris Hopkins&lt;/h3&gt;
&lt;img style=&#34;width: 300px&#34; alt=&#34;Chris Hopkins&#34; src=&#34;/blog/2018/07/meet-the-end-point-windows-consulting-group/chris_hopkins.jpg&#34; /&gt;
&lt;p&gt;Chris has 10 years of experience in IT, network support, and development. He has extensive cross-platform development and hardware knowledge. Chris excels at mail migrations, Active Directory and Windows Server environments, site-to-site website migrations, and cloud-based phone systems.&lt;/p&gt;
&lt;p&gt;Learn more about the &lt;a href=&#34;/expertise/windows-systems/&#34;&gt;services our Windows consulting group offers&lt;/a&gt; and talk to us today about how we can help you too!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Integration Experiences</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2012/06/integration-experiences/"/>
      <id>https://www.endpointdev.com/blog/2012/06/integration-experiences/</id>
      <published>2012-06-13T00:00:00+00:00</published>
      <author>
        <name>David Christensen</name>
      </author>
      <content type="html">
        &lt;img border=&#34;0&#34; src=&#34;/blog/2012/06/integration-experiences/image-0.jpeg&#34; align=&#34;right&#34; /&gt;
&lt;p&gt;Szymon Guz gave a talk which covered his experiences working for a large client on integrating an acquired business’ website and fulfillment processes into their existing backend and fulfillment systems.&lt;/p&gt;
&lt;p&gt;He gave us a detailed overview of their existing shipping fulfillment infrastructure, along with specific technical issues he encountered when integrating with the fulfillment models of the additional website.&lt;/p&gt;
&lt;p&gt;He also detailed some of the non-technical/project management challenges inherent when working with a large number of people on a project.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
