local lunit, RUN = lunit do
RUN   = lunit and function()end or function ()
  local res = lunit.run()
  if res.errors + res.failed > 0 then
    os.exit(-1)
  end
  return os.exit(0)
end
lunit = require "lunit"
end

local TEST_CASE  = assert(lunit.TEST_CASE)
local skip       = lunit.skip or function() end

local curl      = require "lcurl"
local utils     = require "utils"

local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime')

local GET_URL = 'http://127.0.0.1:7090/get'

local null = curl.null

local function is_freed(c)
  return not not string.find(tostring(c), '%(freed%)')
end

local _ENV = TEST_CASE'mime lifetime' if not curl.OPT_MIMEPOST then
function test() skip("MIMI API supports since cURL 7.56.0") end
else

local easy, mime

function setup()
  easy = curl.easy()
end

function teardown()
  if easy then easy:close() end
  if mime then mime:free() end
  easy, mime = nil
end

function test_preserve_mime_part_reference()
  -- mime part stores references to all parts

  local mime, part = easy:mime() do
    part = weak_ptr(mime:addpart())
  end
  gc_collect()

  assert_not_nil(part.value)

  mime = nil
  gc_collect()

  assert_nil(part.value)

  easy:close()
end

function test_free_mime_subparts()
  -- when free root free all nodes

  -- mime
  --  +- part3
  --      +- alt
  --          +- part1
  --          +- part2

  local mime, a, p1, p2, p3 = easy:mime() do

    local alt = easy:mime()

    local part1 = alt:addpart()

    local part2 = alt:addpart()

    local part3 = mime:addpart()
    part3:subparts(alt, "multipart/alternative")

    a  = weak_ptr(alt)
    p1 = weak_ptr(part1)
    p2 = weak_ptr(part2)
    p3 = weak_ptr(part3)
  end

  gc_collect()

  assert_not_nil(a.value)
  assert_not_nil(p1.value)
  assert_not_nil(p2.value)
  assert_not_nil(p3.value)

  -- reamove reference to root node
  mime = nil
  gc_collect(4)

  assert_nil(a.value)
  assert_nil(p1.value)
  assert_nil(p2.value)
  assert_nil(p3.value)

  easy:close()
end

function test_preserve_mime_subparts()
  -- if we have references to subnode but we free root
  -- then all references have to become to invalid

  -- mime
  --  +- part3
  --      +- alt
  --          +- part1
  --          +- part2

  local easy = curl.easy()

  local mime, a, p1, p2, p3 = easy:mime() do

    local alt = easy:mime()

    local part1 = alt:addpart()

    local part2 = alt:addpart()

    local part3 = mime:addpart()
    part3:subparts(alt, "multipart/alternative")

    a  = weak_ptr(alt)
    p1 = weak_ptr(part1)
    p2 = weak_ptr(part2)
    p3 = weak_ptr(part3)
  end

  gc_collect()

  assert_not_nil(a.value)
  assert_not_nil(p1.value)
  assert_not_nil(p2.value)
  assert_not_nil(p3.value)

  -- save reference to subnode
  local subnode = a.value

  mime = nil

  -- in this case call `free` to root node.
  -- there no way to get reference to this node from child
  -- so there no way to use it.
  gc_collect()

  -- libcurl still close all childs
  -- so all reference are invalid

  assert_not_nil(a.value)
  assert_not_nil(is_freed(a.value))
  assert_nil(p1.value)
  assert_nil(p2.value)
  assert_nil(p3.value)

  easy:close()
end

function test_preserve_mime_by_easy()

  local mime do
    mime = weak_ptr(easy:mime())
    easy:setopt_mimepost(mime.value)
  end

  gc_collect()

  assert_not_nil(mime.value)

  easy:unsetopt_mimepost()

  gc_collect()

  assert_nil(mime.value)
end

function test_mime_does_not_ref_to_easy()
  -- exists of mime object does not prevent easy from GC

  local mime = easy:mime()
  local peasy = weak_ptr(easy)
  easy = nil

  gc_collect()

  assert_nil(peasy.value)
end

function test_mimepost_does_not_ref_to_easy()
  -- exists of mime object does not prevent easy from GC

  -- create mime and set it as mimepost option
  local mime = easy:mime()
  easy:setopt_mimepost(mime)

  local peasy = weak_ptr(easy)
  easy = nil

  gc_collect()

  assert_nil(peasy.value)
end

function test_cleanup_on_easy_reset()

  local mime do
    mime = weak_ptr(easy:mime())
    easy:setopt_mimepost(mime.value)
  end

  gc_collect()

  assert_not_nil(mime.value)

  easy:reset()

  gc_collect(10)

  assert_nil(mime.value)

  easy:setopt{url = GET_URL, writefunction = function() end}
  easy:perform()
end

end

local _ENV = TEST_CASE'mime basic' if not curl.OPT_MIMEPOST then
function test() skip("MIMI API supports since cURL 7.56.0") end
else

local easy, mime

local function dump_mime(mime)
  return dump_mime_(easy, mime, GET_URL)
end

function setup()
  easy = curl.easy()
  mime = easy:mime()
end

function teardown()
  if easy then easy:close() end
  if mime then mime:free() end
  easy, mime = nil
end

function test_data()
  mime:addpart():data('hello')
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
end

function test_data_type()
  mime:addpart():data('hello', 'test/html')
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
end

function test_data_type_name()
  mime:addpart():data('hello', 'test/html', 'test')
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-%sname="test"', info)
end

function test_data_name()
  mime:addpart():data('hello', nil, 'test')
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Disposition:.-%sname="test"', info)
end

function test_data_filename()
  mime:addpart():data('hello', nil, nil, 'test.html')
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Disposition:.-%sfilename="test%.html"', info)
end

function test_data_should_not_unset_on_nil()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  })
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)

  part:data('world', nil, 'test2')
  info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nworld', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test2"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)

  part:data('!!!!!', 'text/xml', nil)
  info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\n!!!!!', info)
  assert_match('Content%-Type:%s+text/xml', info)
  assert_match('Content%-Disposition:.-name="test2"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)

  part:data('!!!!!!!', 'text/xml', nil, nil)
  info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\n!!!!!', info)
  assert_match('Content%-Type:%s+text/xml', info)
  assert_match('Content%-Disposition:.-name="test2"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_data_headers()
  mime:addpart():data('hello', {
    'X-Custom-Header: hello'
  })
  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_name()
  mime:addpart():data('hello', 'test/html', 'test'):name(false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_not_match('Content%-Disposition:.-name="test"', info)
end

function test_unset_name_by_null()
  mime:addpart():data('hello', 'test/html', 'test'):name(null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_not_match('Content%-Disposition:.-name="test"', info)
end

function test_unset_type()
  mime:addpart():data('hello', 'test/html'):type(false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('Content%-Type:%s+test/html', info)
end

function test_unset_type_by_null()
  mime:addpart():data('hello', 'test/html'):type(null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('Content%-Type:%s+test/html', info)
end

function test_unset_headers()
  mime:addpart():data('hello', 'test/html',{
    'X-Custom-Header: hello'
  }):headers(false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_headers_by_null()
  mime:addpart():data('hello', 'test/html',{
    'X-Custom-Header: hello'
  }):headers(null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_headers_by_empty_array()
  mime:addpart():data('hello', 'test/html',{
    'X-Custom-Header: hello'
  }):headers({})

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data()
  mime:addpart():data('hello', 'test/html', 'test'):data(false)

  local info = assert_string(dump_mime(mime))
  assert_not_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
end

function test_unset_data_by_null()
  mime:addpart():data('hello', 'test/html', 'test'):data(null)

  local info = assert_string(dump_mime(mime))
  assert_not_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
end

function test_unset_data_type_1()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_type_1_by_null()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_type_2()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', false, nil, nil)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_not_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_name_1()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', nil, false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_not_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_name_1_by_null()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', nil, null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_not_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_name_2()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', nil, false, nil)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_not_match('Content%-Disposition:.-name="test"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_header()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', nil, nil, nil, false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_not_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_header_by_null()
  local part = mime:addpart():data('hello', 'test/html', 'test', {
    'X-Custom-Header: hello'
  }):data('hello', nil, nil, nil, null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-name="test"', info)
  assert_not_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_filename_1()
  local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', {
    'X-Custom-Header: hello'
  }):data('hello', nil, nil, false)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-%sname="test"', info)
  assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_unset_data_filename_1_by_null()
  local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', {
    'X-Custom-Header: hello'
  }):data('hello', nil, nil, null)

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\nhello', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-%sname="test"', info)
  assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
end

function test_fail_pass_nil_as_first_arg()
  local part = mime:addpart()
  assert_error(function() part:data()    end)
  assert_error(function() part:data(nil) end)

  assert_error(function() part:name()    end)
  assert_error(function() part:name(nil) end)

  assert_error(function() part:type()    end)
  assert_error(function() part:type(nil) end)

  assert_error(function() part:headers()    end)
  assert_error(function() part:headers(nil) end)
end

end

local _ENV = TEST_CASE'mime addpart' if not curl.OPT_MIMEPOST then
function test() skip("MIMI API supports since cURL 7.56.0") end
else

local easy, mime

local function dump_mime(mime)
  return dump_mime_(easy, mime, GET_URL)
end

function setup()
  easy = curl.easy()
  mime = easy:mime()
end

function teardown()
  if easy then easy:close() end
  if mime then mime:free() end
  easy, mime = nil
end

function test_empty_table()
  assert(mime:addpart{})
end

function test_pass_args()
  assert(mime:addpart{
    data = 'hello';
    encoder = 'base64';
    name = 'test';
    filename = 'test.html';
    type = 'test/html';
    headers = {
      'X-Custom-Header: hello';
    }
  })

  local info = assert_string(dump_mime(mime))
  assert_match('\r\n\r\naGVsbG8=', info)
  assert_match('Content%-Type:%s+test/html', info)
  assert_match('Content%-Disposition:.-%sname="test"', info)
  assert_not_match('Content%-Disposition:.-%sname="test%.html"', info)
  assert_match('X%-Custom%-Header:%s*hello', info)
  assert_match('Content%-Transfer%-Encoding:%s*base64', info)
end

local function easy_dump_mime(easy, mime, url)
  assert(mime:addpart{
    data = 'hello';
    encoder = 'base64';
    name = 'test';
    filename = 'test.html';
    type = 'test/html';
    headers = {
      'X-Custom-Header: hello';
    }
  })

  easy:setopt{
    mimepost       = mime;
  }

  assert(easy:setopt{
    url            = GET_URL;
    customrequest  = "GET";
    
    writefunction  = function()end;
  })

  if not ok then return nil, err end

  ok, err = easy:perform()

  if not ok then return nil, err end

  return table.concat(buffer)
end

end

RUN()
