diff --git a/scripts/lint.sh b/scripts/lint.sh
index e1602f86117d495a0a52a76538ad37774263bf32..0de7945ed3847736b2a96c98d39f84846abed46a 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -14,4 +14,4 @@ curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh |
 bin/golangci-lint run -E gofmt -E unconvert -E misspell -E whitespace --timeout 2m --max-same-issues 10
 
 npm install eslint@5.16.0 prettier eslint-plugin-prettier eslint-config-cozy-app
-./node_modules/.bin/eslint "assets/scripts/**"
+./node_modules/.bin/eslint "assets/scripts/**" tests/integration/konnector/*.js
diff --git a/tests/integration/Gemfile b/tests/integration/Gemfile
index 3bbffdf95eb4d8b721279eb6cf6e6b0c23ff4cb5..578998ea2ac1241b698d81ffa84da454bcc159a7 100644
--- a/tests/integration/Gemfile
+++ b/tests/integration/Gemfile
@@ -10,3 +10,4 @@ gem "pry"
 gem "pry-rescue"
 gem "pry-stack_explorer"
 gem "rest-client"
+gem "uuid"
diff --git a/tests/integration/Gemfile.lock b/tests/integration/Gemfile.lock
index 9c0d200e150ddd57ff7a7bbde2484b667b7db7a7..f467a9fabb414999dd8c5a1311cdf1c7e2bf649e 100644
--- a/tests/integration/Gemfile.lock
+++ b/tests/integration/Gemfile.lock
@@ -20,6 +20,8 @@ GEM
     i18n (1.0.0)
       concurrent-ruby (~> 1.0)
     interception (0.5)
+    macaddr (1.7.2)
+      systemu (~> 2.6.5)
     method_source (0.9.0)
     mime-types (3.1)
       mime-types-data (~> 3.2015)
@@ -41,9 +43,12 @@ GEM
       http-cookie (>= 1.0.2, < 2.0)
       mime-types (>= 1.16, < 4.0)
       netrc (~> 0.8)
+    systemu (2.6.5)
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.5)
+    uuid (2.3.9)
+      macaddr (~> 1.0)
     websocket-driver (0.7.0)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.3)
@@ -62,6 +67,7 @@ DEPENDENCIES
   pry-rescue
   pry-stack_explorer
   rest-client
+  uuid
 
 BUNDLED WITH
    1.16.4
diff --git a/tests/integration/boot.rb b/tests/integration/boot.rb
index 93b30f7b68c75f6866d78d66421affcba9eacc02..ff795cd7f4a745097b5a7af85274b2c7b7f83ed1 100644
--- a/tests/integration/boot.rb
+++ b/tests/integration/boot.rb
@@ -10,6 +10,7 @@ require 'open3'
 require 'pbkdf2'
 require 'pry'
 require 'rest-client'
+require 'uuid'
 
 AwesomePrint.pry!
 Pry.config.history.file = File.expand_path "../tmp/.pry_history", __FILE__
diff --git a/tests/integration/konnector/index.js b/tests/integration/konnector/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..48aa922c0f7e5cf08de214289bf9e89625a2168f
--- /dev/null
+++ b/tests/integration/konnector/index.js
@@ -0,0 +1,28 @@
+let fs = require('fs')
+let http = require('http')
+
+let fields = JSON.parse(process.env['COZY_FIELDS'])
+let credentials = process.env['COZY_CREDENTIALS']
+let instance = process.env['COZY_URL']
+
+let url = instance + 'data/io.cozy.accounts/' + fields.account
+let options = {
+  headers: {
+    Authorization: `Bearer ${credentials}`
+  }
+}
+
+http.get(url, options, res => {
+  if (res.statusCode !== 200) {
+    throw new Error(`Status Code: ${res.statusCode}`)
+  }
+  res.setEncoding('utf8')
+  let rawData = ''
+  res.on('data', chunk => {
+    rawData += chunk
+  })
+  res.on('end', () => {
+    let data = JSON.parse(rawData)
+    fs.writeFileSync(data.log, JSON.stringify(data, null, '  '))
+  })
+})
diff --git a/tests/integration/konnector/manifest.konnector b/tests/integration/konnector/manifest.konnector
new file mode 100644
index 0000000000000000000000000000000000000000..eb61c85b28b5809bf2b171566c0c85c532aa2489
--- /dev/null
+++ b/tests/integration/konnector/manifest.konnector
@@ -0,0 +1,24 @@
+{
+  "aggregator": {
+    "accountId": "bank-aggregator"
+  },
+  "description": "A konnector to test accounts cleaning",
+  "developer": {
+    "name": "Cozy Cloud",
+    "url": "https://cozy.io/"
+  },
+  "editor": "Cozy Cloud",
+  "language": "node",
+  "license": "MIT",
+  "name": "bankkonn",
+  "on_delete_account": "on_delete.js",
+  "permissions": {
+    "accounts": {
+      "description": "Required to read the aggregator account",
+      "type": "io.cozy.accounts"
+    }
+  },
+  "slug": "bankkonn",
+  "type": "konnector",
+  "version": "0.1.0"
+}
diff --git a/tests/integration/konnector/on_delete.js b/tests/integration/konnector/on_delete.js
new file mode 100644
index 0000000000000000000000000000000000000000..986d571c216c53f36f0c48eb1d7e01971fac8f50
--- /dev/null
+++ b/tests/integration/konnector/on_delete.js
@@ -0,0 +1,39 @@
+let fs = require('fs')
+let http = require('http')
+
+let fields = JSON.parse(process.env['COZY_FIELDS'])
+let credentials = process.env['COZY_CREDENTIALS']
+let instance = process.env['COZY_URL']
+
+let url =
+  instance +
+  'data/io.cozy.accounts/' +
+  fields.account +
+  '?rev=' +
+  fields.account_rev
+let options = {
+  headers: {
+    Authorization: `Bearer ${credentials}`
+  }
+}
+
+http.get(url, options, res => {
+  if (res.statusCode !== 200) {
+    throw new Error(`Status Code: ${res.statusCode}`)
+  }
+  res.setEncoding('utf8')
+  let rawData = ''
+  res.on('data', chunk => {
+    rawData += chunk
+  })
+  res.on('end', () => {
+    let data = JSON.parse(rawData)
+    url = instance + 'data/io.cozy.accounts/' + data.relationships.data._id
+    http.get(url, options, res2 => {
+      if (res2.statusCode !== 200) {
+        throw new Error(`Status Code: ${res2.statusCode}`)
+      }
+      res2.pipe(fs.createWriteStream(data.log))
+    })
+  })
+})
diff --git a/tests/integration/lib/account.rb b/tests/integration/lib/account.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cfdd108fc8b15d8fe21066dcec4b4c19d0634489
--- /dev/null
+++ b/tests/integration/lib/account.rb
@@ -0,0 +1,34 @@
+class Account
+  include Model
+
+  attr_reader :name, :log
+
+  def self.doctype
+    "io.cozy.accounts"
+  end
+
+  def initialize(opts = {})
+    @couch_id = opts[:id]
+    @name = (opts[:name] || Faker::DrWho.character).gsub(/[^A-Za-z]/, '_')
+    @log = opts[:log] || "#{Helpers.current_dir}/account_#{@name}.log"
+    @aggregator = opts[:aggregator]
+    @type = opts[:type]
+  end
+
+  def as_json
+    json = {
+      name: @name,
+      log: @log,
+      account_type: @type
+    }.compact
+    if @aggregator
+      json[:relationships] = {
+        data: {
+          _id: @aggregator.couch_id,
+          _type: @aggregator.doctype
+        }
+      }
+    end
+    json
+  end
+end
diff --git a/tests/integration/lib/album.rb b/tests/integration/lib/album.rb
index 7e2ddb1e5b500c7829d8bcb4de066734e8ff2978..93ef724f3b5854649642ebb9a9f1d7a6ebcf77c0 100644
--- a/tests/integration/lib/album.rb
+++ b/tests/integration/lib/album.rb
@@ -56,11 +56,4 @@ class Album
       created_at: @created_at.rfc3339
     }
   end
-
-  def as_reference
-    {
-      doctype: doctype,
-      id: @couch_id
-    }
-  end
 end
diff --git a/tests/integration/lib/contact.rb b/tests/integration/lib/contact.rb
index 37d9068d7cc2d9c5ae7839816d75ae5ebf91dd3d..93e3b64e1a8b0efec765e834ae4e4a8809432aa1 100644
--- a/tests/integration/lib/contact.rb
+++ b/tests/integration/lib/contact.rb
@@ -62,11 +62,4 @@ class Contact
       phone: @phones
     }
   end
-
-  def as_reference
-    {
-      doctype: doctype,
-      id: @couch_id
-    }
-  end
 end
diff --git a/tests/integration/lib/instance.rb b/tests/integration/lib/instance.rb
index 91185aee650cc1693a8fa9ebedd01ce32ae26ed1..815c172d493dd5516d304f8da7f2983dea142de2 100644
--- a/tests/integration/lib/instance.rb
+++ b/tests/integration/lib/instance.rb
@@ -25,6 +25,18 @@ class Instance
     @stack.install_app self, slug
   end
 
+  def install_konnector(slug, source_url = nil)
+    @stack.install_konnector self, slug, source_url
+  end
+
+  def remove_konnector(slug)
+    @stack.remove_konnector self, slug
+  end
+
+  def run_konnector(slug, account_id)
+    @stack.run_konnector self, slug, account_id
+  end
+
   def client
     @client ||= RestClient::Resource.new url
   end
diff --git a/tests/integration/lib/job.rb b/tests/integration/lib/job.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d80e34eb953e676186026c2e7d7a412b9589aa23
--- /dev/null
+++ b/tests/integration/lib/job.rb
@@ -0,0 +1,28 @@
+class Job
+  include Model
+
+  attr_reader :attributes
+
+  def self.doctype
+    "io.cozy.jobs"
+  end
+
+  def initialize(opts = {})
+    @couch_id = opts["id"]
+    @attributes = opts["attributes"]
+  end
+
+  def done?(inst)
+    status(inst) == "done"
+  end
+
+  def status(inst)
+    opts = {
+      accept: 'application/vnd.api+json',
+      authorization: "Bearer #{inst.token_for doctype}"
+    }
+    res = inst.client["/jobs/#{@couch_id}"].get opts
+    j = JSON.parse(res.body)
+    j.dig("data", "attributes", "state")
+  end
+end
diff --git a/tests/integration/lib/model.rb b/tests/integration/lib/model.rb
index 076c553549c176757be6b2b6f80c5ed246a55e2c..3eaefa004825e5e3143361afc874a4f4253879e7 100644
--- a/tests/integration/lib/model.rb
+++ b/tests/integration/lib/model.rb
@@ -7,10 +7,17 @@ module Model
     end
   end
 
-  def to_json
+  def to_json(*_args)
     JSON.generate as_json
   end
 
+  def as_reference
+    {
+      doctype: doctype,
+      id: @couch_id
+    }
+  end
+
   def save(inst)
     opts = {
       content_type: :json,
@@ -27,6 +34,15 @@ module Model
     @couch_rev = j["rev"]
   end
 
+  def delete(inst)
+    opts = {
+      accept: "application/vnd.api+json",
+      content_type: "application/vnd.api+json",
+      authorization: "Bearer #{inst.token_for doctype}"
+    }
+    inst.client["/data/#{doctype}/#{@couch_id}?rev=#{@couch_rev}"].delete opts
+  end
+
   def doctype
     self.class.doctype
   end
diff --git a/tests/integration/lib/stack.rb b/tests/integration/lib/stack.rb
index b2888164c2bcb4a90a7e4ee8e203cdd5c51576f7..e4f67c5d82449b3c69eae424007cc3c659fd0f0a 100644
--- a/tests/integration/lib/stack.rb
+++ b/tests/integration/lib/stack.rb
@@ -23,6 +23,10 @@ class Stack
     @tokens = {}
   end
 
+  def konnectors_cmd
+    File.expand_path "../../../scripts/konnector-node-run.sh", __dir__
+  end
+
   def start
     vault = File.join Helpers.current_dir, "vault"
     FileUtils.mkdir_p vault
@@ -32,7 +36,8 @@ class Stack
            "--port", @port, "--admin-port", @admin,
            "--fs-url", "file://#{Helpers.current_dir}/",
            "--vault-encryptor-key", "#{vault}/key.enc",
-           "--vault-decryptor-key", "#{vault}/key.dec"]
+           "--vault-decryptor-key", "#{vault}/key.dec",
+           "--konnectors-cmd", konnectors_cmd]
     Helpers.spawn cmd.join(" "), log: "stack-#{@port}.log"
     sleep 1
   end
@@ -68,6 +73,33 @@ class Stack
     @apps[key] = system cmd.join(" ")
   end
 
+  def install_konnector(inst, slug, source_url = nil)
+    cmd = ["cozy-stack", "konnectors", "install",
+           slug, source_url,
+           "--port", @port, "--admin-port", @admin,
+           "--domain", inst.domain, ">", "/dev/null"].compact
+    puts cmd.join(" ").green
+    system cmd.join(" ")
+  end
+
+  def remove_konnector(inst, slug)
+    cmd = ["cozy-stack", "konnectors", "uninstall", slug,
+           "--port", @port, "--admin-port", @admin,
+           "--domain", inst.domain, ">", "/dev/null"]
+    puts cmd.join(" ").green
+    system cmd.join(" ")
+  end
+
+  def run_konnector(inst, slug, account_id)
+    cmd = ["cozy-stack", "konnectors", "run", slug,
+           "--account-id", account_id,
+           "--port", @port, "--admin-port", @admin,
+           "--domain", inst.domain]
+    puts cmd.join(" ").green
+    out = `#{cmd.join(" ")}`.chomp
+    Job.new JSON.parse(out)
+  end
+
   def token_for(inst, doctypes)
     key = inst.domain + "/" + doctypes.join(" ")
     @tokens[key] ||= generate_token_for(inst, doctypes)
diff --git a/tests/integration/lib/trigger.rb b/tests/integration/lib/trigger.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08ad8d50e6413d69aabdcb39ded2820fe361e19a
--- /dev/null
+++ b/tests/integration/lib/trigger.rb
@@ -0,0 +1,26 @@
+class Trigger
+  include Model
+
+  def self.doctype
+    "io.cozy.triggers"
+  end
+
+  def initialize(opts = {})
+    @attributes = opts
+  end
+
+  def save(inst)
+    opts = {
+      content_type: :json,
+      accept: :json,
+      authorization: "Bearer #{inst.token_for doctype}"
+    }
+    res = inst.client["/jobs/triggers"].post to_json, opts
+    j = JSON.parse(res.body)
+    @couch_id = j["data"]["id"]
+  end
+
+  def as_json
+    { data: { attributes: @attributes } }
+  end
+end
diff --git a/tests/integration/tests/accounts_cleaning.rb b/tests/integration/tests/accounts_cleaning.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab0113a587638e42737898e95385f3b483ddb1e1
--- /dev/null
+++ b/tests/integration/tests/accounts_cleaning.rb
@@ -0,0 +1,46 @@
+require_relative '../boot'
+require 'minitest/autorun'
+require 'pry-rescue/minitest' unless ENV['CI']
+
+def wait_for_file(file)
+  10.times do
+    sleep 1
+    return if File.exist? file
+  end
+end
+
+describe "An io.cozy.accounts" do
+  it "is cleaned via on_delete_account" do
+    Helpers.scenario "accounts_cleaning"
+    Helpers.start_mailhog
+
+    inst = Instance.create name: "Isabelle"
+    Account.create inst, name: "not a bank account"
+    source_url = "file://" + File.expand_path("../konnector", __dir__)
+
+    # 1. When an account is deleted, it is cleaned.
+    inst.install_konnector "bankone", source_url
+    aggregator = Account.create inst, id: ["bank-aggregator", UUID.generate].sample
+    accone = Account.create inst, type: "bankone", aggregator: aggregator,
+                                  name: Faker::HarryPotter.character
+    Trigger.create inst, worker: "konnector", type: "@cron", arguments: "@monthly",
+                         message: { konnector: "bankone", account: accone.couch_id }
+
+    job = inst.run_konnector "bankone", accone.couch_id
+    done = false
+    10.times do
+      sleep 1
+      done = job.done?(inst)
+      break if done
+    end
+    assert done
+    executed = JSON.parse File.read(accone.log)
+    assert_equal executed["_id"], accone.couch_id
+    File.delete accone.log
+
+    accone.delete inst
+    wait_for_file accone.log
+    executed = JSON.parse File.read(accone.log)
+    assert_equal executed["_id"], aggregator.couch_id
+  end
+end