From 8b839d329e0a5bd954b164c6bb1d78021362c21b Mon Sep 17 00:00:00 2001
From: Julien Ripouteau <julien@ripouteau.com>
Date: Sun, 10 Dec 2023 04:53:01 +0100
Subject: [PATCH] fix: release connection in provider shutdown (#977)

---
 providers/database_provider.ts |  8 ++++
 test/database_provider.spec.ts | 84 ++++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100644 test/database_provider.spec.ts

diff --git a/providers/database_provider.ts b/providers/database_provider.ts
index 0e38c6d8..5f4c40e4 100644
--- a/providers/database_provider.ts
+++ b/providers/database_provider.ts
@@ -110,4 +110,12 @@ export default class DatabaseServiceProvider {
     await this.registerReplBindings()
     await this.registerVineJSRules(db)
   }
+
+  /**
+   * Gracefully close connections during shutdown
+   */
+  async shutdown() {
+    const db = await this.app.container.make('lucid.db')
+    await db.manager.closeAll()
+  }
 }
diff --git a/test/database_provider.spec.ts b/test/database_provider.spec.ts
new file mode 100644
index 00000000..85300dee
--- /dev/null
+++ b/test/database_provider.spec.ts
@@ -0,0 +1,84 @@
+import { test } from '@japa/runner'
+import { IgnitorFactory } from '@adonisjs/core/factories'
+
+import { defineConfig } from '../src/define_config.js'
+import { Database } from '../src/database/main.js'
+
+const BASE_URL = new URL('./tmp/', import.meta.url)
+const IMPORTER = (filePath: string) => {
+  if (filePath.startsWith('./') || filePath.startsWith('../')) {
+    return import(new URL(filePath, BASE_URL).href)
+  }
+  return import(filePath)
+}
+
+test.group('Database Provider', () => {
+  test('register database provider', async ({ assert }) => {
+    const ignitor = new IgnitorFactory()
+      .merge({
+        rcFileContents: {
+          providers: [() => import('../providers/database_provider.js')],
+        },
+      })
+      .withCoreConfig()
+      .withCoreProviders()
+      .merge({
+        config: {
+          database: defineConfig({
+            connection: 'sqlite',
+            connections: {
+              sqlite: {
+                client: 'sqlite',
+                connection: { filename: new URL('./tmp/database.sqlite', import.meta.url).href },
+                migrations: { naturalSort: true, paths: ['database/migrations'] },
+              },
+            },
+          }),
+        },
+      })
+      .create(BASE_URL, { importer: IMPORTER })
+
+    const app = ignitor.createApp('web')
+    await app.init()
+    await app.boot()
+
+    assert.instanceOf(await app.container.make('lucid.db'), Database)
+  })
+
+  test('release connection when app is terminating', async ({ assert }) => {
+    const ignitor = new IgnitorFactory()
+      .merge({
+        rcFileContents: {
+          providers: [() => import('../providers/database_provider.js')],
+        },
+      })
+      .withCoreConfig()
+      .withCoreProviders()
+      .merge({
+        config: {
+          database: defineConfig({
+            connection: 'sqlite',
+            connections: {
+              sqlite: {
+                client: 'sqlite',
+                connection: { filename: new URL('./tmp/database.sqlite', import.meta.url).href },
+                migrations: { naturalSort: true, paths: ['database/migrations'] },
+              },
+            },
+          }),
+        },
+      })
+      .create(BASE_URL, { importer: IMPORTER })
+
+    const app = ignitor.createApp('web')
+    await app.init()
+    await app.boot()
+
+    const db = await app.container.make('lucid.db')
+
+    await db.from('users').catch(() => {})
+    await app.terminate()
+
+    assert.isFalse(db.manager.isConnected('sqlite'))
+  })
+})