// Protocol tests for NNTP. These actually aren't too important, but their main
// purpose is to make sure that maild is working properly and to provide
// examples for how using maild. They also help make sure that I coded Nntpd.sys.mjs
// right, both logically and for RFC compliance.
// TODO:
// * We need to hook up mochitest,
// * TLS negotiation.

// The basic daemon to use for testing Nntpd.sys.mjs implementations
var daemon = setupNNTPDaemon();

// NNTP SERVER TESTS
// -----------------
// Functions in order as defined in Nntpd.sys.mjs. Each function tests the URLs
// that are located over the implementation of nsNNTPProtocol::LoadURL and
// added in bug 400331. Furthermore, they are tested in rough order as they
// would be expected to be used in a session. If more URL types are modified,
// please add a corresponding type to the following tests.
// When adding new servers, only test the commands that become different for
// each specified server, to keep down redudant tests.

function testRFC977() {
  var server = makeServer(NNTP_RFC977_handler, daemon);
  server.start(NNTP_PORT);

  try {
    var prefix = "news://localhost:" + NNTP_PORT + "/";
    var transaction;

    // Test - group subscribe listing
    test = "news:*";
    setupProtocolTest(NNTP_PORT, prefix + "*");
    server.performTest();
    transaction = server.playTransaction();
    do_check_transaction(transaction, ["MODE READER", "LIST"]);

    // Test - getting group headers
    test = "news:test.subscribe.empty";
    server.resetTest();
    setupProtocolTest(NNTP_PORT, prefix + "test.subscribe.empty");
    server.performTest();
    transaction = server.playTransaction();
    do_check_transaction(transaction, [
      "MODE READER",
      "GROUP test.subscribe.empty",
    ]);

    // Test - getting an article
    test = "news:MESSAGE_ID";
    server.resetTest();
    setupProtocolTest(NNTP_PORT, prefix + "TSS1@nntp.invalid");
    server.performTest();
    transaction = server.playTransaction();
    do_check_transaction(transaction, [
      "MODE READER",
      "ARTICLE <TSS1@nntp.invalid>",
    ]);
  } catch (e) {
    dump("NNTP Protocol test " + test + " failed for type RFC 977:\n");
    try {
      var trans = server.playTransaction();
      if (trans) {
        dump("Commands called: " + trans.them + "\n");
      }
    } catch (exp) {}
    do_throw(e);
  }
  server.stop();

  var thread = Services.tm.currentThread;
  while (thread.hasPendingEvents()) {
    thread.processNextEvent(true);
  }
}

function testConnectionLimit() {
  var server = makeServer(NNTP_RFC977_handler, daemon);
  server.start(NNTP_PORT);
  // 1 is the default, but other tests do change it, so let's be explicit.
  _server.maximumConnectionsNumber = 1;

  var prefix = "news://localhost:" + NNTP_PORT + "/";

  // To test make connections limit, we run two URIs simultaneously.
  var url = Services.io.newURI(prefix + "*");
  _server.loadNewsUrl(url, null, null);
  setupProtocolTest(NNTP_PORT, prefix + "TSS1@nntp.invalid");
  server.performTest();
  // We should have length one... which means this must be a transaction object,
  // containing only us and them
  // (playTransactions() returns an array of transaction objects if there is
  // more than one of them, so this assert will fail in that case).
  Assert.ok("us" in server.playTransaction());
  server.stop();

  var thread = Services.tm.currentThread;
  while (thread.hasPendingEvents()) {
    thread.processNextEvent(true);
  }
}

function testReentrantClose() {
  // What we are testing is that a CloseConnection that spins the event loop
  // does not cause a crash.
  var server = makeServer(NNTP_RFC977_handler, daemon);
  server.start(NNTP_PORT);

  var listener = {
    OnStartRunningUrl() {},
    OnStopRunningUrl() {
      // Spin the event loop (entering nsNNTPProtocol::ProcessProtocolState)
      const thread = Services.tm.currentThread;
      while (thread.hasPendingEvents()) {
        thread.processNextEvent(true);
      }
    },
  };
  // Nice multi-step command--we can close while executing this URL if we are
  // careful.
  var url = Services.io.newURI(
    "news://localhost:" + NNTP_PORT + "/test.filter"
  );
  url.QueryInterface(Ci.nsIMsgMailNewsUrl);
  url.RegisterListener(listener);

  _server.loadNewsUrl(url, null, {
    QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
    onStartRequest() {},
    onStopRequest() {},
  });
  server.performTest("GROUP");
  dump("Stopping server\n");
  Services.tm.currentThread.dispatch(
    {
      run() {
        _server.closeCachedConnections();
      },
    },
    Ci.nsIEventTarget.DISPATCH_NORMAL
  );
  server.performTest();
  server.stop();

  // Break refcnt loops
  listener = url = null;
}

function testManyConnections() {
  // Start up 2 connections at once and make sure that they don't conflict
  var server = makeServer(NNTP_RFC2980_handler, daemon);
  setupLocalServer(NNTP_PORT);
  server.start(NNTP_PORT);
  _server.maximumConnectionsNumber = 3;
  var listener = {
    ran: 0,
    OnStartRunningUrl() {},
    OnStopRunningUrl() {
      if (--this.ran == 0) {
        _server.closeCachedConnections();
      }
    },
  };
  for (const group of _server.rootFolder.subFolders) {
    group.getNewMessages(null, listener);
    listener.ran++;
  }
  server.performTest();
  // The last one that is processed is test.filter, so make sure that
  // test.subscribed.simple is not retrieving the data meant for test.filter
  const folder = _server.rootFolder.getChildNamed("test.subscribe.simple");
  Assert.equal(folder.getTotalMessages(false), 1);
}

function run_test() {
  testRFC977();
  testConnectionLimit();
  testReentrantClose();
  testManyConnections();
}
