diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/test | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/base/test')
78 files changed, 10870 insertions, 0 deletions
diff --git a/comm/mailnews/base/test/.eslintrc.js b/comm/mailnews/base/test/.eslintrc.js new file mode 100644 index 0000000000..5816519fbb --- /dev/null +++ b/comm/mailnews/base/test/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/valid-jsdoc"], +}; diff --git a/comm/mailnews/base/test/TestMsgStripRE.cpp b/comm/mailnews/base/test/TestMsgStripRE.cpp new file mode 100644 index 0000000000..eae217dcb2 --- /dev/null +++ b/comm/mailnews/base/test/TestMsgStripRE.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISupportsPrimitives.h" +#include "nsString.h" + +#define STRING_SIZE 255 +struct testInfo { + char encodedInput[STRING_SIZE]; + char expectedOutput[STRING_SIZE]; + bool expectedDidModify; +}; + +int testStripRe(const char* encodedInput, char* expectedOutput, + bool expectedDidModify) { + // call NS_StripRE with the appropriate args + nsCString modifiedSubject; + bool didModify; + didModify = NS_MsgStripRE(nsDependentCString(encodedInput), modifiedSubject); + + // make sure we got the right results + if (didModify != expectedDidModify) return 2; + + if (didModify) { + if (strcmp(expectedOutput, modifiedSubject.get())) { + return 3; + } + } else if (strcmp(expectedOutput, encodedInput)) { + return 4; + } + + // test passed + return 0; +} + +// int main(int argc, char** argv) +TEST(TestMsgStripRE, TestMsgStripREMain) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + // set localizedRe pref, value "SV,ÆØÅ", + // \xC3\x86, \xC3\x98 and \xC3\x85 are the UTF-8 encodings of Æ, Ø and Å. + rv = prefBranch->SetStringPref("mailnews.localizedRe", + "SV,\xC3\x86\xC3\x98\xC3\x85"_ns); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + // run our tests + struct testInfo testInfoStructs[] = { + // Note that re-encoding always happens in UTF-8. + {"SV: =?ISO-8859-1?Q?=C6blegr=F8d?=", "=?UTF-8?B?w4ZibGVncsO4ZA==?=", + true}, + {"=?ISO-8859-1?Q?SV=3A=C6blegr=F8d?=", "=?UTF-8?B?w4ZibGVncsO4ZA==?=", + true}, + + // Note that in the next two tests, the only ISO-8859-1 chars are in the + // localizedRe piece, so once they've been stripped, the re-encoding + // process simply writes out ASCII rather than an ISO-8859-1 encoded + // string with no actual ISO-8859-1 special characters, which seems + // reasonable. + {"=?ISO-8859-1?Q?=C6=D8=C5=3A_Foo_bar?=", "Foo bar", true}, + {"=?ISO-8859-1?Q?=C6=D8=C5=3AFoo_bar?=", "Foo bar", true}}; + + bool allTestsPassed = true; + int result; + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(testInfoStructs); i++) { + result = testStripRe(testInfoStructs[i].encodedInput, + testInfoStructs[i].expectedOutput, + testInfoStructs[i].expectedDidModify); + if (result) { + printf("Failed: %s, i=%d | result=%d\n", __FILE__, i, result); + allTestsPassed = false; + } + EXPECT_TRUE(result == 0); + } + + if (allTestsPassed) { + printf("all tests passed\n"); + } + EXPECT_TRUE(allTestsPassed); +} diff --git a/comm/mailnews/base/test/gtest/TestHeaderReader.cpp b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp new file mode 100644 index 0000000000..326279655a --- /dev/null +++ b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp @@ -0,0 +1,205 @@ +#include "gtest/gtest.h" +#include "nsString.h" +#include "HeaderReader.h" +#include "nsTArray.h" +#include "mozilla/ArrayUtils.h" + +// Invocation: +// $ ./mach gtest "TestHeaderReader.*" + +TEST(TestHeaderReader, Basic) +{ + struct hdr { + nsCString name; + nsCString rawValue; + }; + + struct { + nsCString raw; + nsTArray<hdr> expected; + bool isComplete; // expect to find complete header block? + nsCString leftOver; // Data which should be left unprocessed. + } testMsgs[] = { + // Simple case works? + { + "Message-ID: one\r\n" + "To: alice@example.com\r\n" + "\r\n" + "Body here. HeaderReader should have stopped by now.\r\n" + "Note: this line looks like a header, but it isn't!\r\n"_ns, + { + {"Message-ID"_ns, "one"_ns}, + {"To"_ns, "alice@example.com"_ns}, + }, + true, + "Body here. HeaderReader should have stopped by now.\r\n" + "Note: this line looks like a header, but it isn't!\r\n"_ns, + }, + // Handle folded header values correctly? + { + "To: bob@example.com\r\n" + "Subject: Do\r\n" + " we\r\n" + " handle\r\n" + "\tfolded\r\n" // Can fold with tabs too. + " fields OK?\r\n" + "Message-ID: two\r\n" + "\r\n" + "...message body here...\r\n"_ns, + { + {"To"_ns, "bob@example.com"_ns}, + {"Subject"_ns, + "Do\r\n we\r\n handle\r\n\tfolded\r\n fields OK?"_ns}, + {"Message-ID"_ns, "two"_ns}, + }, + true, + "...message body here...\r\n"_ns, + }, + // Handle no whitespace after colon? + { + "Foo:bar\r\n" + "\r\n"_ns, + { + {"Foo"_ns, "bar"_ns}, + }, + true, + ""_ns, + }, + // Folding with no text on first line? + // (I _think_ this is legal...) + { + "Foo: \r\n" + " bar\r\n" + "\r\n"_ns, + { + {"Foo"_ns, "\r\n bar"_ns}, + }, + true, + ""_ns, + }, + // Folded line with no end of header block. + // Input could be truncated. So we don't want this to output any headers + // (The missing next line could be folded or not - we just don't know). + { + "Foo:\r\n" + " bar\r\n" + " wibble\r\n"_ns, + {}, + false, + "Foo:\r\n bar\r\n wibble\r\n"_ns, + }, + // Ignore incomplete lines as expected?. + { + "Foo: bar\r\n" + "Wibble: this is a part"_ns, // ... "ial line". + { + {"Foo"_ns, "bar"_ns}, + }, + false, + "Wibble: this is a part"_ns, + }, + // Ignore incomplete folded lines? + { + "Foo: bar\r\n" + "Wibble: this\r\n" + " value is not co"_ns, // ... "mplete". + { + {"Foo"_ns, "bar"_ns}, + }, + false, + "Wibble: this\r\n value is not co"_ns, + }, + // Handle empty input without crashing? + { + ""_ns, + {}, + false, + ""_ns, + }}; + + for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) { + auto const& t = testMsgs[i]; + // Collect all the headers. + nsTArray<HeaderReader::Hdr> gotHeaders; + HeaderReader rdr; + auto handler = [&](HeaderReader::Hdr const& hdr) { + gotHeaders.AppendElement(hdr); + return true; // Keep going. + }; + + // Simulate multiple passes over a growing buffer - we'll add a quarter + // of the data each pass. + for (uint32_t i = 1; i < 4; ++i) { + // Encourage each pass to use different memory. + nsCString fudge(t.raw.BeginReading(), i * t.raw.Length() / 4); + rdr.Parse(fudge, handler); + } + + // Last pass - give the reader the entire input. + auto leftOver = rdr.Parse(t.raw, handler); + + // Did we get all the headers we expected? + ASSERT_EQ(gotHeaders.Length(), t.expected.Length()); + for (size_t i = 0; i < t.expected.Length(); ++i) { + auto const& expect = t.expected[i]; + auto const& got = gotHeaders[i]; + ASSERT_EQ(expect.name, nsCString(got.Name(t.raw))); + ASSERT_EQ(expect.rawValue, nsCString(got.RawValue(t.raw))); + } + + // Correctly detected the end of the header block? + ASSERT_EQ(t.isComplete, rdr.IsComplete()); + + // Make sure processing stopped where expected. + ASSERT_EQ(t.leftOver, nsCString(leftOver)); + } +} + +// Check that callback can halt processing, and that it can be correctly +// resumed. +TEST(TestHeaderReader, Stop) +{ + // We'll stop each time we find a header name containing "STOP". + struct { + nsCString raw; + int expectedCount; // Number of headers we expect to find. + int expectedPasses; // Number of passes we expect to run. + } testMsgs[] = { + {"foo: bar\r\n\r\n"_ns, 1, 1}, + {"Message-ID: one\r\n" + "STOP: pause here please\r\n" + "foo-One: nothing to see here...\r\n" + "foo-Two: still nothing...\r\n" + "\r\n"_ns, + 4, 2}, + {"A: eh\r\n" + "B: bee\r\n" + "STOP_1: pause here.\r\n" + "C: sea\r\n" + "STOP_2: another pause.\r\n" + "E: eee\r\n" + "STOP_3: last one.\r\n" + "\r\n"_ns, + 7, 3}, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) { + auto const& t = testMsgs[i]; + HeaderReader rdr; + int gotCount = 0; + auto handler = [&](HeaderReader::Hdr const& hdr) { + ++gotCount; + return nsCString(hdr.Name(t.raw)).Find("STOP") == kNotFound; + }; + + int passes = 0; + // All our examples have a complete header block. + while (!rdr.IsComplete()) { + rdr.Parse(t.raw, handler); + ++passes; + } + ASSERT_EQ(gotCount, t.expectedCount); + ASSERT_EQ(passes, t.expectedPasses); + ASSERT_TRUE(rdr.IsComplete()); + } +} diff --git a/comm/mailnews/base/test/gtest/TestLineReader.cpp b/comm/mailnews/base/test/gtest/TestLineReader.cpp new file mode 100644 index 0000000000..e84274fcb0 --- /dev/null +++ b/comm/mailnews/base/test/gtest/TestLineReader.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "nsString.h" +#include "LineReader.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Span.h" + +// Invocation: +// $ ./mach gtest "TestLineReader.*" + +TEST(TestLineReader, Basic) +{ + struct { + nsTArray<nsCString> chunks; + int expectedLines; + } testCases[] = { + // Split lines as expected? + { + {"Line one\r\nLine two\r\n"_ns}, + 2, + }, + + // Plain LFs should be accepted. + { + {"Line one\nLine two\n"_ns}, + 2, + }, + + // Empty lines appear as expected? + { + {"\r\n\r\n\r\n"_ns}, + 3, + }, + + // Empty case handled? + { + {""_ns}, + 0, + }, + + // Split in mid-CRLF. + { + { + "EOL split across\r"_ns, + "\nchunks.\r\n"_ns, + }, + 2, + }, + + // Chunks join up correctly? + { + {"This single line is "_ns, "fed in as "_ns, + "multiple chunks\r\n"_ns}, + 1, + }, + // Handle an empty chunk OK? + { + {"foo"_ns, ""_ns, + "bar\n"_ns + "wibble\n"_ns}, + 2, + }, + // Handle lines without EOL? + { + {"This line has no EOL and relies on Flush()."_ns}, + 1, + }, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) { + auto const& t = testCases[i]; + + // Join chunks into one string - we expect the output to be + // this, byte-for-byte. + nsCString expectedText; + for (auto chunk : t.chunks) { + expectedText.Append(chunk); + } + + // Callback to collect the lines and count them. + int gotLineCount = 0; + nsCString gotText; + auto callback = [&](mozilla::Span<const char> line) { + ++gotLineCount; + gotText.Append(line); + return true; + }; + // Parse the chunks. + LineReader chopper; + for (auto chunk : t.chunks) { + chopper.Feed(chunk, callback); + } + chopper.Flush(callback); + + ASSERT_EQ(t.expectedLines, gotLineCount); + ASSERT_EQ(expectedText, gotText); + } +} + +// Check that processing is aborted when callback returns false. +TEST(TestLineReader, Stop) +{ + struct { + nsTArray<nsCString> chunks; + int expectedLines; + } testCases[] = { + // Stop at 2 lines. + { + {"Line one\nSTOP\nThis line is never seen.\n"_ns}, + 2, + }, + + // Line split up over multiple chunks. + { + {"one\r\nST"_ns, "OP\r\nblah blah\r\n"_ns}, + 2, + }, + + // Empty string -> no lines. + { + {""_ns}, + 0, + }, + + // No EOL, relies on Flush(). + { + { + "STOP"_ns, + }, + 1, + }, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) { + auto const& t = testCases[i]; + + // Callback to collect the lines and count them, stopping when + // we find a line containing "STOP". + int gotLineCount = 0; + auto callback = [&](mozilla::Span<const char> line) -> bool { + ++gotLineCount; + return nsCString(line).Find("STOP"_ns) == kNotFound; + }; + // Parse. + LineReader chopper; + for (auto chunk : t.chunks) { + chopper.Feed(chunk, callback); + } + chopper.Flush(callback); + ASSERT_EQ(t.expectedLines, gotLineCount); + } +} + +// Test the SplitLines() fn. +TEST(TestLineReader, SplitLines) +{ + struct { + nsCString input; + nsTArray<nsCString> expect; // The lines we expect to see. + nsCString expectLeftover; // The unconsumed data we expect at the end. + } testCases[] = { + // Empty string -> no lines. + {""_ns, {}, ""_ns}, + // Incomplete line + {"foo"_ns, {}, "foo"_ns}, + // Blank lines split as expected? + {"\r\n\r\n\r\n"_ns, {"\r\n"_ns, "\r\n"_ns, "\r\n"_ns}, ""_ns}, + // A couple of normal-looking lines. + {"one\r\ntwo\r\n"_ns, {"one\r\n"_ns, "two\r\n"_ns}, ""_ns}, + // Handles bare LFs? + {"one\ntwo\n"_ns, {"one\n"_ns, "two\n"_ns}, ""_ns}, + // Ignores bare CRs? + {"one\rtwo\r"_ns, {}, "one\rtwo\r"_ns}, + + // Early-out works? + {"one\r\nSTOP\r\n3\r\n4\r\n"_ns, + {"one\r\n"_ns, "STOP\r\n"_ns}, + "3\r\n4\r\n"_ns}, + }; + + for (auto const& t : testCases) { + nsTArray<nsCString> got; + auto fn = [&](mozilla::Span<const char> line) -> bool { + got.AppendElement(line); + // Finish early if line contains "STOP". + return nsCString(line).Find("STOP"_ns) == kNotFound; + }; + mozilla::Span<const char> leftover = SplitLines(t.input, fn); + + ASSERT_EQ(t.expect.Length(), got.Length()); + for (size_t i = 0; i < t.expect.Length(); ++i) { + ASSERT_EQ(t.expect[i], got[i]); + } + ASSERT_EQ(t.expectLeftover, nsCString(leftover)); + } +} diff --git a/comm/mailnews/base/test/gtest/moz.build b/comm/mailnews/base/test/gtest/moz.build new file mode 100644 index 0000000000..a0b6129efd --- /dev/null +++ b/comm/mailnews/base/test/gtest/moz.build @@ -0,0 +1,15 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "xul-gtest" + +UNIFIED_SOURCES += [ + "TestHeaderReader.cpp", + "TestLineReader.cpp", +] + +# LOCAL_INCLUDES += [ +# "../../src", +# ] diff --git a/comm/mailnews/base/test/moz.build b/comm/mailnews/base/test/moz.build new file mode 100644 index 0000000000..4bb676ffb3 --- /dev/null +++ b/comm/mailnews/base/test/moz.build @@ -0,0 +1,19 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell-imap.ini", "unit/xpcshell.ini"] + +FINAL_LIBRARY = "xul-gtest" + +UNIFIED_SOURCES += [ + "TestMsgStripRE.cpp", +] + +LOCAL_INCLUDES += [ + "/netwerk/test", + "/xpcom/tests", +] + +TEST_DIRS += ["gtest"] diff --git a/comm/mailnews/base/test/unit/data/folderCache.json b/comm/mailnews/base/test/unit/data/folderCache.json new file mode 100644 index 0000000000..e593e6cbb9 --- /dev/null +++ b/comm/mailnews/base/test/unit/data/folderCache.json @@ -0,0 +1,206 @@ +{ + "/foo/bar/profile-default/ImapMail/imap.localhost": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": -1, + "totalUnreadMsgs": -1 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Archives.msf": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "Archives", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Drafts.msf": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "Drafts", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/INBOX.msf": { + "MRMTime": "1628202592", + "MRUTime": "1628202593", + "aclFlags": 0, + "boxFlags": 2097216, + "expungedBytes": 11536, + "flags": 2282237972, + "folderSize": 22906, + "hierDelim": 46, + "lastSyncTimeInSec": 0, + "nextUID": 2, + "onlineName": "INBOX", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 2, + "serverUnseen": 0, + "totalMsgs": 2, + "totalUnreadMsgs": 1 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Junk.msf": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "Junk", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Sent.msf": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "Sent", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Templates.msf": { + "aclFlags": 0, + "boxFlags": 0, + "expungedBytes": 0, + "flags": 0, + "folderSize": -1, + "hierDelim": 94, + "onlineName": "Templates", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/Trash.msf": { + "aclFlags": 0, + "boxFlags": 262224, + "expungedBytes": 0, + "flags": 532756, + "folderSize": -1, + "hierDelim": 46, + "onlineName": "Trash", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 0, + "serverUnseen": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/blah.msf": { + "MRUTime": "1628202572", + "aclFlags": 0, + "boxFlags": 262208, + "expungedBytes": 0, + "flags": 134750228, + "folderSize": 11872, + "hierDelim": 46, + "lastSyncTimeInSec": 0, + "nextUID": 3, + "onlineName": "blah", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 2, + "serverUnseen": 0, + "totalMsgs": 2, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/ImapMail/imap.localhost/foo.msf": { + "MRUTime": "1628202573", + "aclFlags": 0, + "boxFlags": 262208, + "expungedBytes": 11536, + "flags": 134750228, + "folderSize": 10707667, + "hierDelim": 46, + "lastSyncTimeInSec": 0, + "nextUID": 5, + "onlineName": "foo", + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "serverRecent": 0, + "serverTotal": 4, + "serverUnseen": 0, + "totalMsgs": 3, + "totalUnreadMsgs": 0 + }, + "/foo/bar/profile-default/Mail/Local Folders": { + "expungedBytes": 0, + "flags": 28, + "folderName": "Local Folders", + "folderSize": -1, + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "totalMsgs": -1, + "totalUnreadMsgs": -1 + }, + "/foo/bar/profile-default/Mail/Local Folders/Trash.msf": { + "MRUTime": "1628202569", + "expungedBytes": 0, + "flags": 260, + "folderName": "Trash", + "folderSize": 0, + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0, + "useServerRetention": "1" + }, + "/foo/bar/profile-default/Mail/Local Folders/Unsent Messages.msf": { + "MRUTime": "1628202570", + "expungedBytes": 0, + "flags": 2052, + "folderName": "Outbox", + "folderSize": 0, + "pendingMsgs": 0, + "pendingUnreadMsgs": 0, + "totalMsgs": 0, + "totalUnreadMsgs": 0, + "useServerRetention": "1" + } +} diff --git a/comm/mailnews/base/test/unit/data/panacea.dat b/comm/mailnews/base/test/unit/data/panacea.dat new file mode 100644 index 0000000000..214176e31a --- /dev/null +++ b/comm/mailnews/base/test/unit/data/panacea.dat @@ -0,0 +1,70 @@ +// <!-- <mdb:mork:z v="1.4"/> --> +< <(a=c)> // (f=iso-8859-1) + (8A=boxFlags)(8B=hierDelim)(8C=onlineName)(8D=aclFlags)(8E=serverTotal) + (8F=serverUnseen)(90=serverRecent)(91=indexingPriority)(92=folderName) + (93=applyIncomingFilters.empty)(94=applyIncomingFilters)(95=MRUTime) + (96=dobayes.mailnews@mozilla.org#junk.empty) + (97=dobayes.mailnews@mozilla.org#junk)(98=nextUID) + (99=lastSyncTimeInSec)(9A=useServerRetention)(9B=MRMTime) + (80=ns:msg:db:row:scope:folders:all)(81=ns:msg:db:table:kind:folders) + (82=key)(83=flags)(84=totalMsgs)(85=totalUnreadMsgs) + (86=pendingUnreadMsgs)(87=pendingMsgs)(88=expungedBytes)(89=folderSize)> + +<(80 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/INBOX.msf)(A6=88083014)(AA=2)(A8=1)(81=0)(B8=2d10)(BB=597a) + (A7=200040)(9D=2e)(84=INBOX)(BA=1628202593)(B5=1628202592)(86 + =/foo/bar/profile-default/Mail/Loc\ +al Folders/Trash.msf)(87=104)(88=Trash)(AB=1628202569)(89 + =/foo/bar/profile-default/Mail/Loc\ +al Folders/Unsent Messages.msf)(8A=804)(8C=Outbox)(AC=1628202570)(8D + =/foo/bar/profile-default/Mail/Loc\ +al Folders)(8E=1c)(8F=ffffffff)(82=ffffffffffffffff)(90=Local Folders) + (91 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Drafts.msf)(83=5e)(92=Drafts)(93 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Templates.msf)(94=Templates)(95 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Sent.msf)(96=Sent)(97 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Archives.msf)(98=Archives)(9A + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Trash.msf)(9B=82114)(9C=40050)(9E + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/blah.msf)(A0=8082014)(AF=2e60)(A1=40040)(9F=blah)(AE + =1628202572)(B0=3)(A2 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/foo.msf)(B9=a362d3)(A3=foo)(B2=4)(B1=1628202573)(B4=5) + (A4 + =/foo/bar/profile-default/ImapMail\ +/imap.localhost/Junk.msf)(A5=Junk)(BC + =/foo/bar/profile-default/ImapMail\ +/imap.localhost)(BD=)> +{1:^80 {(k^81:c)(s=9)} + [1(^82^80)(^83^A6)(^84=2)(^85=1)(^86=0)(^87=0)(^88^B8)(^89^BB)(^8A^A7) + (^8B=2e)(^8C^84)(^8D=0)(^8E=2)(^8F=0)(^90=0)(^95^BA)(^98=2)(^99=0) + (^9B^B5)] + [2(^82^86)(^83^87)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89=0)(^92^88) + (^95^AB)(^9A=1)] + [3(^82^89)(^83^8A)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89=0)(^92^8C) + (^95^AC)(^9A=1)] + [4(^82^8D)(^83=1c)(^84^8F)(^85^8F)(^86=0)(^87=0)(^88=0)(^89^82)(^92^90)] + [5(^82^91)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C^92)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [6(^82^93)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C^94)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [7(^82^95)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C^96)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [8(^82^97)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C^98)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [9(^82^9A)(^83^9B)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A^9C) + (^8B=2e)(^8C^88)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [A(^82^9E)(^83^A0)(^84=2)(^85=0)(^86=0)(^87=0)(^88=0)(^89^AF)(^8A^A1) + (^8B=2e)(^8C^9F)(^8D=0)(^8E=2)(^8F=0)(^90=0)(^95^AE)(^98=3)(^99=0)] + [B(^82^A2)(^83^A0)(^84=3)(^85=0)(^86=0)(^87=0)(^88^B8)(^89^B9)(^8A^A1) + (^8B=2e)(^8C^A3)(^8D=0)(^8E=4)(^8F=0)(^90=0)(^95^B1)(^98=5)(^99=0)] + [C(^82^A4)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C^A5)(^8D=0)(^8E=0)(^8F=0)(^90=0)] + [D(^82^BC)(^83=0)(^84^8F)(^85^8F)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0) + (^8B=5e)(^8C=)(^8D=0)(^8E=0)(^8F=0)(^90=0)]} diff --git a/comm/mailnews/base/test/unit/data/panacea_empty.dat b/comm/mailnews/base/test/unit/data/panacea_empty.dat new file mode 100644 index 0000000000..b169b34da9 --- /dev/null +++ b/comm/mailnews/base/test/unit/data/panacea_empty.dat @@ -0,0 +1 @@ +// <!-- <mdb:mork:z v="1.4"/> --> diff --git a/comm/mailnews/base/test/unit/data/remoteContent.sql b/comm/mailnews/base/test/unit/data/remoteContent.sql new file mode 100644 index 0000000000..042cfefbd9 --- /dev/null +++ b/comm/mailnews/base/test/unit/data/remoteContent.sql @@ -0,0 +1,41 @@ +-- Address book with remote content permissions for use in test_accountMigration.js. +PRAGMA user_version = 1; + +CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER); +CREATE TABLE properties (card TEXT, name TEXT, value TEXT); +CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT); +CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card)); + +INSERT INTO cards (uid, localId) VALUES + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 1), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 2), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 3); + +INSERT INTO properties (card, name, value) VALUES + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PrimaryEmail', 'no@test.invalid'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PhotoType', 'generic'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LowercasePrimaryEmail', 'no@test.invalid'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PopularityIndex', '0'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferMailFormat', '0'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LastModifiedDate', '0'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferDisplayName', '1'), + ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'AllowRemoteContent', '0'), + + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PrimaryEmail', 'yes@test.invalid'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PhotoType', 'generic'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LowercasePrimaryEmail', 'yes@test.invalid'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PopularityIndex', '0'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferMailFormat', '0'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LastModifiedDate', '0'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferDisplayName', '1'), + ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'AllowRemoteContent', '0'), + + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LastModifiedDate', '1397383824'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PrimaryEmail', 'yes@test.invalid'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PhotoType', 'generic'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'SecondEmail', 'yes2@test.invalid'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LowercasePrimaryEmail', 'yes@test.invalid'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PopularityIndex', '0'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferMailFormat', '0'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferDisplayName', '1'), + ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'AllowRemoteContent', '1'); diff --git a/comm/mailnews/base/test/unit/head_mailbase.js b/comm/mailnews/base/test/unit/head_mailbase.js new file mode 100644 index 0000000000..9f37623291 --- /dev/null +++ b/comm/mailnews/base/test/unit/head_mailbase.js @@ -0,0 +1,23 @@ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +var { localAccountUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/LocalAccountUtils.jsm" +); + +var CC = Components.Constructor; + +// Ensure the profile directory is set up +do_get_profile(); + +var gDEPTH = "../../../../"; + +registerCleanupFunction(function () { + load(gDEPTH + "mailnews/resources/mailShutdown.js"); +}); diff --git a/comm/mailnews/base/test/unit/nodelist_test.xml b/comm/mailnews/base/test/unit/nodelist_test.xml new file mode 100644 index 0000000000..e1640b5b5b --- /dev/null +++ b/comm/mailnews/base/test/unit/nodelist_test.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" ?> + +<rootnode> + <node id="childnode1" /> + <node id="childnode2" /> + <node id="childnode3" /> +</rootnode> diff --git a/comm/mailnews/base/test/unit/test_MsgIncomingServer.js b/comm/mailnews/base/test/unit/test_MsgIncomingServer.js new file mode 100644 index 0000000000..184d2d4072 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_MsgIncomingServer.js @@ -0,0 +1,224 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Services.prefs.setBoolPref("mailnews.imap.jsmodule", true); + +registerCleanupFunction(() => { + Services.logins.removeAllLogins(); + Services.prefs.clearUserPref("mailnews.imap.jsmodule"); +}); + +/** + * Test password is migrated when changing hostname/username. + */ +add_task(function testMigratePasswordOnChangeUsernameHostname() { + // Add two logins. + let loginItems = [ + ["news://news.localhost", "user-nntp", "password-nntp"], + ["mailbox://pop3.localhost", "user-pop", "password-pop"], + ]; + for (let [uri, username, password] of loginItems) { + let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance( + Ci.nsILoginInfo + ); + login.init(uri, null, uri, username, password, "", ""); + Services.logins.addLogin(login); + } + + // Create a nntp server, check the password can be found correctly. + let nntpIncomingServer = MailServices.accounts.createIncomingServer( + "user-nntp", + "news.localhost", + "nntp" + ); + nntpIncomingServer.getPasswordWithUI("", ""); + equal(nntpIncomingServer.password, "password-nntp"); + + // Change the username, check password can be found using the new username. + nntpIncomingServer.username = "nntp"; + let password; + let serverUri = "news://news.localhost"; + for (let login of Services.logins.findLogins(serverUri, "", serverUri)) { + if (login.username == "nntp") { + password = login.password; + } + } + equal(password, "password-nntp"); + + // Create a pop3 server, check the password can be found correctly. + let pop3IncomingServer = MailServices.accounts.createIncomingServer( + "user-pop", + "pop3.localhost", + "pop3" + ); + pop3IncomingServer.getPasswordWithUI("", ""); + equal(pop3IncomingServer.password, "password-pop"); + + // Change the hostname, check password can be found using the new hostname. + pop3IncomingServer.hostName = "localhost"; + serverUri = "mailbox://localhost"; + for (let login of Services.logins.findLogins(serverUri, "", serverUri)) { + if (login.username == "user-pop") { + password = login.password; + } + } + equal(password, "password-pop"); +}); + +/** + * Test identity folders are migrated when changing hostname/username. + */ +add_task(function testMigrateIdentitiesOnChangeUsernameHostname() { + // Create an imap server. + let incomingServer1 = MailServices.accounts.createIncomingServer( + "user-imap", + "imap.localhost", + "imap" + ); + // Create a pop server. + let incomingServer2 = MailServices.accounts.createIncomingServer( + "user-pop", + "pop3.localhost", + "pop3" + ); + + // Create an identity and point folders to incomingServer1. + let identity1 = MailServices.accounts.createIdentity(); + identity1.fccFolder = incomingServer1.serverURI + "/Sent"; + identity1.draftFolder = incomingServer1.serverURI + "/Drafts"; + identity1.archiveFolder = incomingServer1.serverURI + "/Archives"; + identity1.stationeryFolder = incomingServer1.serverURI + "/Templates"; + let account1 = MailServices.accounts.createAccount(); + account1.addIdentity(identity1); + // Create another identity and point folders to both servers. + let identity2 = MailServices.accounts.createIdentity(); + identity2.fccFolder = incomingServer1.serverURI + "/Sent"; + identity2.draftFolder = incomingServer2.serverURI + "/Drafts"; + let account2 = MailServices.accounts.createAccount(); + account2.addIdentity(identity2); + + // Check folders were correctly set. + equal(identity1.fccFolder, "imap://user-imap@imap.localhost/Sent"); + equal(identity1.draftFolder, "imap://user-imap@imap.localhost/Drafts"); + equal(identity1.archiveFolder, "imap://user-imap@imap.localhost/Archives"); + equal( + identity1.stationeryFolder, + "imap://user-imap@imap.localhost/Templates" + ); + equal(identity2.fccFolder, "imap://user-imap@imap.localhost/Sent"); + equal(identity2.draftFolder, "mailbox://user-pop@pop3.localhost/Drafts"); + + // Change the hostname. + incomingServer1.hostName = "localhost"; + + // Check folders were correctly updated. + identity1 = MailServices.accounts.getIdentity(identity1.key); + equal(identity1.fccFolder, "imap://user-imap@localhost/Sent"); + equal(identity1.draftFolder, "imap://user-imap@localhost/Drafts"); + equal(identity1.archiveFolder, "imap://user-imap@localhost/Archives"); + equal(identity1.stationeryFolder, "imap://user-imap@localhost/Templates"); + equal(identity2.fccFolder, "imap://user-imap@localhost/Sent"); + equal(identity2.draftFolder, "mailbox://user-pop@pop3.localhost/Drafts"); +}); + +/** + * Test spam action prefs are migrated when changing hostname/username. + */ +add_task(function testMigrateSpamActionsOnChangeUsernameHostname() { + // Create an imap server. + let incomingServer1 = MailServices.accounts.createIncomingServer( + "user-imap", + "imap.localhost", + "imap" + ); + incomingServer1.setUnicharValue( + "spamActionTargetFolder", + incomingServer1.serverURI + "/Спам" + ); + + equal( + incomingServer1.spamSettings.actionTargetAccount, + "imap://user-imap@imap.localhost" + ); + equal( + incomingServer1.spamSettings.actionTargetFolder, + "imap://user-imap@imap.localhost/Спам" + ); + + // Change the username. + incomingServer1.username = "user"; + + equal( + incomingServer1.spamSettings.actionTargetAccount, + "imap://user@imap.localhost" + ); + equal( + incomingServer1.spamSettings.actionTargetFolder, + "imap://user@imap.localhost/Спам" + ); +}); + +/** + * Test filters are migrated when changing hostname/username. + */ +add_task(function testMigrateFiltersOnChangeUsernameHostname() { + // Create a nntp server. + let nntpIncomingServer = MailServices.accounts.createIncomingServer( + "user-nntp", + "news.localhost", + "nntp" + ); + let filterList = nntpIncomingServer.getFilterList(null); + + // Insert a CopyToFolder filter. + let filter = filterList.createFilter("filter1"); + let action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.CopyToFolder; + action.targetFolderUri = "news://user-nntp@news.localhost/dest1"; + filter.appendAction(action); + filterList.insertFilterAt(filterList.filterCount, filter); + + // Insert a MarkRead filter. + filter = filterList.createFilter("filter2"); + action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.MarkRead; + filter.appendAction(action); + filterList.insertFilterAt(filterList.filterCount, filter); + + // Insert a MoveToFolder filter. + filter = filterList.createFilter("filter3"); + action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.MoveToFolder; + action.targetFolderUri = "news://user-nntp@news.localhost/dest2"; + filter.appendAction(action); + filterList.insertFilterAt(filterList.filterCount, filter); + + // Change the hostname, test targetFolderUri of filters are changed accordingly. + nntpIncomingServer.hostName = "localhost"; + filterList = nntpIncomingServer.getFilterList(null); + filter = filterList.getFilterAt(0); + equal( + filter.sortedActionList[0].targetFolderUri, + "news://user-nntp@localhost/dest1" + ); + filter = filterList.getFilterAt(2); + equal( + filter.sortedActionList[0].targetFolderUri, + "news://user-nntp@localhost/dest2" + ); + + // Change the username, test targetFolderUri of filters are changed accordingly. + nntpIncomingServer.username = "nntp"; + filterList = nntpIncomingServer.getFilterList(null); + filter = filterList.getFilterAt(0); + equal( + filter.sortedActionList[0].targetFolderUri, + "news://nntp@localhost/dest1" + ); + filter = filterList.getFilterAt(2); + equal( + filter.sortedActionList[0].targetFolderUri, + "news://nntp@localhost/dest2" + ); +}); diff --git a/comm/mailnews/base/test/unit/test_MsgKeySet.js b/comm/mailnews/base/test/unit/test_MsgKeySet.js new file mode 100644 index 0000000000..7ae1eea938 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_MsgKeySet.js @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { MsgKeySet } = ChromeUtils.import("resource:///modules/MsgKeySet.jsm"); + +/** + * Test MsgKeySet.addRange works correctly. + */ +add_task(function testAddRange() { + // Init an empty set. + let keySet = new MsgKeySet(); + ok(!keySet.has(1)); + + // Add two ranges. + keySet.addRange(90, 99); + keySet.addRange(2, 19); + + // Test members. + ok(!keySet.has(1)); + ok(keySet.has(2)); + ok(keySet.has(16)); + ok(!keySet.has(20)); + ok(keySet.has(99)); + equal(keySet.toString(), "2-19,90-99"); + + // Init a set from a string. + keySet = new MsgKeySet("102,199"); + ok(!keySet.has(22)); + ok(keySet.has(199)); + + // Add two ranges. + keySet.addRange(2, 19); + keySet.addRange(12, 29); + + // Test members. + ok(keySet.has(2)); + ok(keySet.has(22)); + ok(keySet.has(199)); + equal(keySet.toString(), "2-29,102,199"); +}); + +/** + * Test MsgKeySet.add works correctly. + */ +add_task(function testAdd() { + // Init an empty set. + let keySet = new MsgKeySet(); + ok(!keySet.has(1)); + + // Add three values. + keySet.add(1); + keySet.add(2); + keySet.add(4); + + // Test members. + ok(keySet.has(1)); + ok(keySet.has(2)); + ok(!keySet.has(3)); + ok(keySet.has(4)); + equal(keySet.toString(), "1-2,4"); +}); + +/** + * Test MsgKeySet.getLastMissingRange works correctly. + */ +add_task(function testGetLastMissingRange() { + // Init a set. + let keySet = new MsgKeySet("2-9,12-29"); + + // Test `start` should be a value not already in keySet. + let [start, end] = keySet.getLastMissingRange(2, 33); + equal(start, 30); + equal(end, 33); + + // Test `start` should be the lowest input value. + [start, end] = keySet.getLastMissingRange(33, 33); + equal(start, 33); + equal(end, 33); + + // Test get missing old messages range works. + [start, end] = keySet.getLastMissingRange(3, 23); + equal(start, 10); + equal(end, 11); +}); diff --git a/comm/mailnews/base/test/unit/test_accountMgr.js b/comm/mailnews/base/test/unit/test_accountMgr.js new file mode 100644 index 0000000000..6036dccfa9 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMgr.js @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that we cleanup the account prefs when the account manager is + * loaded. This entails removing duplicate accounts from + * mail.accountmanager.accounts list, and removing duplicate accounts with + * the same server. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create account prefs with both kinds of duplication. + + Services.prefs.setCharPref("mail.account.account1.identities", "id1"); + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.identities", "id2"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.account.account4.identities", "id2"); + Services.prefs.setCharPref("mail.account.account4.server", "server4"); + Services.prefs.setCharPref("mail.account.account5.identities", "id3"); + Services.prefs.setCharPref("mail.account.account5.server", "server5"); + Services.prefs.setCharPref("mail.account.account6.identities", "id3"); + Services.prefs.setCharPref("mail.account.account6.server", "server5"); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + Services.prefs.setCharPref("mail.server.server2.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server2.type", "none"); + Services.prefs.setCharPref("mail.server.server2.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server2.directory-rel", + "[ProfD]Mail/Local Folders-1" + ); + Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org"); + Services.prefs.setCharPref("mail.server.server4.type", "pop3"); + Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org"); + Services.prefs.setCharPref("mail.server.server5.type", "pop3"); + Services.prefs.setCharPref( + "mail.server.server5.deferred_to_account", + "account2" + ); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account4,account5,account5,account6,account1,account2" + ); + // Set the default account to one we're going to get rid of. The account + // manager should recover relatively gracefully. + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account6"); + + // This will force the load of the accounts setup above. + Assert.equal(MailServices.accounts.accounts.length, 3); + // Here all the accounts are local but the first account will behave as + // an actual local account and will be kept last always. + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account4,account5,account1" + ); + let server5 = MailServices.accounts + .getIncomingServer("server5") + .QueryInterface(Ci.nsIPop3IncomingServer); + Assert.equal(server5.deferredToAccount, "account1"); + + // Just make sure this doesn't throw an exception, because we did remove the + // default account. + let defaultAccount = MailServices.accounts.defaultAccount; + Assert.equal(defaultAccount, null); + + // Remove an account, and verify that the account list pref looks OK: + let server = MailServices.accounts.getIncomingServer("server4"); + + // We need to get the root folder to read from the folder cache + // before it gets removed or else we'll assert, because we're + // not completely initialized... + server.rootFolder.flags; + + MailServices.accounts.removeAccount( + MailServices.accounts.FindAccountForServer(server) + ); + Assert.equal(MailServices.accounts.accounts.length, 2); + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account5,account1" + ); + // Make sure cleaning up duplicate accounts didn't hork accounts. + Assert.equal( + Services.prefs.getCharPref("mail.account.account1.server"), + "server1" + ); + Assert.equal( + Services.prefs.getCharPref("mail.account.account5.server"), + "server5" + ); +} diff --git a/comm/mailnews/base/test/unit/test_accountMgr2.js b/comm/mailnews/base/test/unit/test_accountMgr2.js new file mode 100644 index 0000000000..a48685da00 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMgr2.js @@ -0,0 +1,199 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests various methods and attributes on nsIMsgAccountManager. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +add_task(async function () { + // Create a couple of test accounts. + let acc1 = MailServices.accounts.createAccount(); + acc1.incomingServer = MailServices.accounts.createIncomingServer( + "bob_imap", + "imap.example.com", + "imap" + ); + let id1 = MailServices.accounts.createIdentity(); + id1.email = "bob_imap@example.com"; + acc1.addIdentity(id1); + + let acc2 = MailServices.accounts.createAccount(); + acc2.incomingServer = MailServices.accounts.createIncomingServer( + "bob_pop3", + "pop3.EXAMPLE.com.", + "pop3" + ); + let id2 = MailServices.accounts.createIdentity(); + id2.email = "bob_pop3@example.com"; + acc2.addIdentity(id2); + + // Add an identity shared by both accounts. + let id3 = MailServices.accounts.createIdentity(); + id3.email = "bob_common@example.com"; + acc1.addIdentity(id3); + acc2.addIdentity(id3); + + // The special "Local Folders" account and server (server type is "none"). + MailServices.accounts.createLocalMailAccount(); + + // Setup done. Now check that things are as we expect. + + // At this point we should have 3 accounts and servers (imap, pop, local). + Assert.equal(MailServices.accounts.accounts.length, 3); + Assert.equal(MailServices.accounts.allServers.length, 3); + + // The identities we explicitly created. + Assert.equal(MailServices.accounts.allIdentities.length, 3); + + // Check we find the right number of identities associated with each server. + Assert.equal( + MailServices.accounts.getIdentitiesForServer(acc1.incomingServer).length, + 2 + ); + Assert.equal( + MailServices.accounts.getIdentitiesForServer(acc2.incomingServer).length, + 2 + ); + Assert.equal( + MailServices.accounts.getIdentitiesForServer( + MailServices.accounts.localFoldersServer + ).length, + 0 + ); + + // id1 and id2 are on separate accounts (and servers). + Assert.equal(MailServices.accounts.getServersForIdentity(id1).length, 1); + Assert.equal(MailServices.accounts.getServersForIdentity(id2).length, 1); + // id3 is shared. + Assert.equal(MailServices.accounts.getServersForIdentity(id3).length, 2); + + // Does allFolders return the default folders we'd expect? + // IMAP has Inbox only. + // POP3 and local accounts both have Inbox and Trash. + Assert.equal(MailServices.accounts.allFolders.length, 1 + 2 + 2); + + // Let's ditch the IMAP account. + MailServices.accounts.removeAccount(acc1); + + Assert.equal(MailServices.accounts.accounts.length, 2); + Assert.equal(MailServices.accounts.allServers.length, 2); + + // It should have taken the imap-specific identity with it. + Assert.equal(MailServices.accounts.allIdentities.length, 2); + + // Test a special hostname. + const acc4 = MailServices.accounts.createAccount(); + acc4.incomingServer = MailServices.accounts.createIncomingServer( + "bob_unavail", + "0.0.0.0.", // Note ending dot which would not do anything for an IP. + "pop3" + ); + let id4 = MailServices.accounts.createIdentity(); + id4.email = "bob_unavail@example.com"; + acc4.addIdentity(id4); + + Assert.equal( + MailServices.accounts.accounts.length, + 3, + "acc4 should be in accounts" + ); + + // Test that an account with empty server hostname doesn't even get listed. + const serverKey = acc4.incomingServer.key; + Services.prefs.setStringPref(`mail.server.${serverKey}.hostname`, ""); + MailServices.accounts.unloadAccounts(); + MailServices.accounts.loadAccounts(); + Assert.equal( + MailServices.accounts.accounts.length, + 2, + "invalid acc4 should have been removed" + ); + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account2,account3", + "listed accounts should be correct after testing blank host" + ); + + // Test that an account that had punycode hostname entered is found. + const acc5 = MailServices.accounts.createAccount(); + acc5.incomingServer = MailServices.accounts.createIncomingServer( + "bob_imap5", + "xn--thnderbird-beb.test", + "imap" + ); + const id5 = MailServices.accounts.createIdentity(); + id5.email = "bob_imap5@xn--thnderbird-beb.test"; + acc5.addIdentity(id5); + + MailServices.accounts.unloadAccounts(); + MailServices.accounts.loadAccounts(); + + Assert.equal( + MailServices.accounts.accounts.length, + 3, + "added acc5 should still be listed" + ); + + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account2,account5,account3", + "listed accounts should be correct after testing punycode host" + ); + const punyServer = MailServices.accounts.findServerByURI( + Services.io.newURI("imap://xn--thnderbird-beb.test:143/INBOX") + ); + Assert.ok( + punyServer?.hostName, + "should find server by uri for punycode hostname" + ); + + const punyServer2 = MailServices.accounts.findServerByURI( + Services.io.newURI("imap://thünderbird.test:143/INBOX") + ); + Assert.ok( + punyServer2?.hostName, + "should find ACE server by normalized IDN hostname" + ); + + // Test that an account with IDN hostname entered is found. + const acc6 = MailServices.accounts.createAccount(); + acc6.incomingServer = MailServices.accounts.createIncomingServer( + "bob_imap6", + "thünderbird.example", + "imap" + ); + const id6 = MailServices.accounts.createIdentity(); + id6.email = "bob_imap6@thünderbird.example"; + acc6.addIdentity(id6); + + MailServices.accounts.unloadAccounts(); + MailServices.accounts.loadAccounts(); + + Assert.equal( + MailServices.accounts.accounts.length, + 4, + "added acc6 should still be listed" + ); + + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account2,account5,account6,account3", + "listed accounts should be correct after testing IDN host" + ); + const idnServer = MailServices.accounts.findServerByURI( + Services.io.newURI("imap://thünderbird.example:143/INBOX") + ); + Assert.ok(idnServer?.hostName, "should find server by uri for IDN hostname"); + + const idnServer2 = MailServices.accounts.findServerByURI( + Services.io.newURI("imap://xn--thnderbird-beb.example:143/INBOX") + ); + Assert.ok( + idnServer2?.hostName, + "should find idn server by by ACE encodeed uri" + ); +}); diff --git a/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js b/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js new file mode 100644 index 0000000000..f8b8f707c7 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that accounts with invalid types, such as could be created + * from an extension, do not disappear immediately when the extension + * is unloaded. + * + * Adapted from test_AccountMgr.js by Kent James <kent@caspia.com> + */ + +function run_test() { + Services.prefs.setCharPref("mail.account.account1.identities", "id1"); + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.identities", "id2"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + + // Here we are simulating a server and account that is added by an + // extension, but that extension is currently unloaded. The extension + // added "secondsToLeaveUnavailable" (though a typical value would be + // one month, not 2 seconds!) to tell the core code to leave this alone + // for awhile if the extension is unloaded. + Services.prefs.setCharPref("mail.server.server2.hostname", "pop3.host.org"); + Services.prefs.setCharPref("mail.server.server2.type", "invalid"); + Services.prefs.setIntPref("mail.server.server2.secondsToLeaveUnavailable", 2); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account2,account1" + ); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1"); + + // This will force the load of the accounts setup above. + // We don't see the invalid account. + Assert.equal(MailServices.accounts.accounts.length, 1); + + // But it is really there. + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account2,account1" + ); + + // Add a new account (so that we can check if this clobbers the existing + // inactive account or its server). + let newAccount = MailServices.accounts.createAccount(); + let newIdentity = MailServices.accounts.createIdentity(); + newAccount.addIdentity(newIdentity); + newAccount.defaultIdentity = newIdentity; + newAccount.incomingServer = MailServices.accounts.createIncomingServer( + "somename", + "somehost.example.com", + "pop3" + ); + + // No collisions with the inactive account. + Assert.notEqual(newIdentity.key, "id2"); + Assert.notEqual(newAccount.incomingServer.key, "server2"); + Assert.notEqual(newAccount.key, "account2"); + Assert.equal(MailServices.accounts.accounts.length, 2); + + MailServices.accounts.unloadAccounts(); + + // Set the unavailable account to a valid type, and watch it appear. + Services.prefs.setCharPref("mail.server.server2.type", "pop3"); + Assert.equal(MailServices.accounts.accounts.length, 3); + + // Make it bad again, and reload it to restart the timeout before delete. + MailServices.accounts.unloadAccounts(); + Services.prefs.setCharPref("mail.server.server2.type", "invalid"); + Assert.equal(MailServices.accounts.accounts.length, 2); + MailServices.accounts.unloadAccounts(); + + // Now let the bad type timeout, and watch it magically disappear! + do_test_pending(); + do_timeout(3000, function () { + Assert.equal(MailServices.accounts.accounts.length, 2); + + // It is now gone. + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + newAccount.key + ",account1" + ); + + do_test_finished(); + }); +} diff --git a/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js b/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js new file mode 100644 index 0000000000..5972012e85 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create account prefs with Local Folders in the middle + Services.prefs.setCharPref("mail.account.account1.identities", "id1"); + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account4.identities", "id2"); + Services.prefs.setCharPref("mail.account.account4.server", "server4"); + Services.prefs.setCharPref("mail.account.account5.identities", "id3"); + Services.prefs.setCharPref("mail.account.account5.server", "server5"); + Services.prefs.setCharPref("mail.account.account6.identities", "id3"); + Services.prefs.setCharPref("mail.account.account6.server", "server5"); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org"); + Services.prefs.setCharPref("mail.server.server4.type", "pop3"); + Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org"); + Services.prefs.setCharPref("mail.server.server5.type", "pop3"); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account4,account1,account5" + ); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account4"); + + // This will force the load of the accounts setup above. + Assert.equal(MailServices.accounts.accounts.length, 3); + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account4,account1,account5" + ); +} diff --git a/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js b/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js new file mode 100644 index 0000000000..640c314499 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that if the default account is removed, the default becomes + * another account or null. The removed account must not remain the default. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create account prefs. + + Services.prefs.setCharPref("mail.account.account1.identities", "id1"); + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.identities", "id2"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.account.account3.identities", "id3"); + Services.prefs.setCharPref("mail.account.account3.server", "server3"); + + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server2.hostname", "host2.invalid"); + Services.prefs.setCharPref("mail.server.server2.type", "pop3"); + Services.prefs.setCharPref("mail.server.server3.hostname", "host3.invalid"); + Services.prefs.setCharPref("mail.server.server3.type", "pop3"); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account2,account3,account1" + ); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account3"); + + // Load of the accounts setup above. + Assert.equal(MailServices.accounts.accounts.length, 3); + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account2,account3,account1" + ); + Assert.equal(MailServices.accounts.defaultAccount?.key, "account3"); + + // Remove the default account, account3. The default should be set to a + // sensible replacement, account2. + + MailServices.accounts.removeAccount(MailServices.accounts.defaultAccount); + Assert.equal(MailServices.accounts.defaultAccount?.key, "account2"); + + // Remove the default account, account2. No remaining accounts can be the + // default, so it should become null. + + MailServices.accounts.removeAccount(MailServices.accounts.defaultAccount); + Assert.equal(MailServices.accounts.defaultAccount, null); +} diff --git a/comm/mailnews/base/test/unit/test_accountMigration.js b/comm/mailnews/base/test/unit/test_accountMigration.js new file mode 100644 index 0000000000..d2bb801c49 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_accountMigration.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that we don't try to reset the mail.server.server<n>.authMethod + * preference every time we run the migration code, and other migration stuff + */ + +var { migrateMailnews } = ChromeUtils.import( + "resource:///modules/MailnewsMigrator.jsm" +); + +/* import-globals-from ../../../test/resources/abSetup.js */ +load("../../../resources/abSetup.js"); + +function testPermission(aURI) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + aURI, + {} + ); + return Services.perms.testPermissionFromPrincipal(principal, "image"); +} + +function run_test() { + // Set up some basic accounts with limited prefs - enough to satisfy the + // migrator. + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.account.account3.server", "server3"); + + // Server1 has nothing set. + + // Server2 has useSecAuth set to true, auth_login unset + Services.prefs.setBoolPref("mail.server.server2.useSecAuth", true); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account1,account2,account3" + ); + + // Set server1 and server2 username and hostname to test clientid population. + Services.prefs.setCharPref("mail.server.server1.userName", "testuser1"); + Services.prefs.setCharPref("mail.server.server2.userName", "testuser2"); + Services.prefs.setCharPref("mail.server.server1.type", "imap"); + Services.prefs.setCharPref("mail.server.server2.type", "imap"); + Services.prefs.setCharPref( + "mail.server.server1.hostname", + "mail.sampledomain1.com" + ); + Services.prefs.setCharPref( + "mail.server.server2.hostname", + "mail.sampledomain2.com" + ); + + loadABFile("data/remoteContent", kPABData.fileName); + + let uriAllowed = Services.io.newURI( + "chrome://messenger/content/email=yes@test.invalid" + ); + let uriAllowed2 = Services.io.newURI( + "chrome://messenger/content/email=yes2@test.invalid" + ); + let uriDisallowed = Services.io.newURI( + "chrome://messenger/content/email=no@test.invalid" + ); + + // Check that this email that according to the ab data has (had!) + // remote content premissions, has no premissions pre migration. + Assert.equal(testPermission(uriAllowed), Services.perms.UNKNOWN_ACTION); + Assert.equal(testPermission(uriAllowed2), Services.perms.UNKNOWN_ACTION); + Assert.equal(testPermission(uriDisallowed), Services.perms.UNKNOWN_ACTION); + + // Now migrate the prefs. + migrateMailnews(); + + // Check that server1 and server2 have the same clientid. + Assert.ok(Services.prefs.prefHasUserValue("mail.server.server1.clientid")); + Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.clientid")); + + // Check that server3 didn't get a clientid, since it has no userName. + // This is the case for nntp. + Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server3.clientid")); + + // Check what has been set. + Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server1.authMethod")); + Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.authMethod")); + Assert.equal( + Services.prefs.getIntPref("mail.server.server2.authMethod"), + Ci.nsMsgAuthMethod.secure + ); + + // Now clear the authMethod for set for server2. This simulates the user + // setting the value back to "3", i.e. Ci.nsMsgAuthMethod.passwordCleartext. + Services.prefs.clearUserPref("mail.server.server2.authMethod"); + + // Now attempt migration again, e.g. a second load of TB + migrateMailnews(); + + // This time around, both of these should not be set. + Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server1.authMethod")); + Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server2.authMethod")); + + // + // Now check SMTP + // + + Services.prefs.setCharPref("mail.smtpservers", "smtp1,smtp2,smtp3"); + + // smtp1 has nothing set. + + // smtp2 has useSecAuth set to true, auth_method unset + Services.prefs.setBoolPref("mail.smtpserver.smtp2.useSecAuth", true); + + // Set server1 and server2 username and hostname to test clientid population. + Services.prefs.setCharPref("mail.smtpserver.smtp1.username", "testuser1"); + Services.prefs.setCharPref("mail.smtpserver.smtp2.username", "testuser2"); + Services.prefs.setCharPref( + "mail.smtpserver.smtp1.hostname", + "mail.sampledomain1.com" + ); + Services.prefs.setCharPref( + "mail.smtpserver.smtp2.hostname", + "mail.sampledomain2.com" + ); + Services.prefs.setCharPref( + "mail.smtpserver.smtp3.hostname", + "mail.nousername.example.com" + ); + + // Migration should now have added permissions for the address that had them + // and not for the one that didn't have them. + Assert.ok(Services.prefs.getIntPref("mail.ab_remote_content.migrated") > 0); + Assert.equal(testPermission(uriAllowed), Services.perms.ALLOW_ACTION); + Assert.equal(testPermission(uriAllowed2), Services.perms.ALLOW_ACTION); + Assert.equal(testPermission(uriDisallowed), Services.perms.UNKNOWN_ACTION); + + // Now migrate the prefs + migrateMailnews(); + + // Check that smtpserver 1 and smtpserver 2 now have a clientid. + Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.clientid")); + Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.clientid")); + + // Check that the smtp3server 2 got a clientid, even if it has no + // username. All SMTP servers don't require a username. + Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp3.clientid")); + + Assert.ok( + !Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.authMethod") + ); + Assert.ok( + Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.authMethod") + ); + Assert.equal( + Services.prefs.getIntPref("mail.smtpserver.smtp2.authMethod"), + Ci.nsMsgAuthMethod.secure + ); + + // Now clear the authMethod for set for smtp2. This simulates the user + // setting the value back to "3", i.e. Ci.nsMsgAuthMethod.passwordCleartext. + Services.prefs.clearUserPref("mail.smtpserver.smtp2.authMethod"); + + // Now clear the mail.server.server1.clientid to test re-population. + Services.prefs.clearUserPref("mail.server.server2.clientid"); + + // Now attempt migration again, e.g. a second load of TB + migrateMailnews(); + + // This time around, both of these should not be set. + Assert.ok( + !Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.authMethod") + ); + Assert.ok( + !Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.authMethod") + ); + + // The server2 clientid should be the same as the smtpserver2 now since + // they are for the same mail.sampledomain2.com domain. + Assert.equal( + Services.prefs.getCharPref("mail.smtpserver.smtp2.clientid"), + Services.prefs.getCharPref("mail.server.server2.clientid") + ); +} diff --git a/comm/mailnews/base/test/unit/test_acctRepair.js b/comm/mailnews/base/test/unit/test_acctRepair.js new file mode 100644 index 0000000000..b702dcfd61 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_acctRepair.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that we recover from having a local folders server + * without having an account that points at it. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create account prefs with both kinds of duplication. + + Services.prefs.setCharPref("mail.account.account2.identities", "id2"); + Services.prefs.setCharPref("mail.account.account2.server", "server1"); + Services.prefs.setCharPref("mail.account.account6.identities", "id3"); + Services.prefs.setCharPref("mail.account.account6.server", "server5"); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org"); + Services.prefs.setCharPref("mail.server.server5.type", "pop3"); + Services.prefs.setCharPref( + "mail.server.server5.deferred_to_account", + "account2" + ); + + Services.prefs.setCharPref("mail.accountmanager.accounts", "account6"); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account6"); + Services.prefs.setCharPref( + "mail.accountmanager.localfoldersserver", + "server1" + ); + // This will force the load of the accounts setup above. + // We should have created an account for the local folders. + Assert.equal(MailServices.accounts.accounts.length, 2); + Assert.equal( + Services.prefs.getCharPref("mail.accountmanager.accounts"), + "account6,account7" + ); + Assert.equal( + Services.prefs.getCharPref("mail.account.account7.server"), + "server1" + ); + let server5 = MailServices.accounts + .getIncomingServer("server5") + .QueryInterface(Ci.nsIPop3IncomingServer); + Assert.equal(server5.deferredToAccount, "account7"); +} diff --git a/comm/mailnews/base/test/unit/test_bccInDatabase.js b/comm/mailnews/base/test/unit/test_bccInDatabase.js new file mode 100644 index 0000000000..ad9ff039b7 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_bccInDatabase.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Testing of bcc in message summary file added in bug 481667 + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var hdr; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + var copyListener = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey); + }, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + continueTest(); + }, + }; + + // Get a message into the local filestore. + var draft = do_get_file("../../../data/draft1"); + do_test_pending(); + MailServices.copy.copyFileMessage( + draft, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + null + ); +} + +function continueTest() { + // dump("\nbccList >" + hdr.bccList); + // dump("\nccList >" + hdr.ccList); + // dump("\n"); + Assert.ok(hdr.bccList.includes("Another Person")); + Assert.ok(hdr.bccList.includes("<u1@example.com>")); + Assert.ok(!hdr.bccList.includes("IDoNotExist")); + hdr = null; + do_test_finished(); +} diff --git a/comm/mailnews/base/test/unit/test_bug428427.js b/comm/mailnews/base/test/unit/test_bug428427.js new file mode 100644 index 0000000000..f00f2bd571 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_bug428427.js @@ -0,0 +1,249 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test of message count changes in virtual folder views + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var bugmail1 = do_get_file("../../../data/bugmail1"); +// main test + +// the headers for the test messages. All messages are identical, but +// have different properties set on them. +var hdrs = []; + +// how many identical messages to load +var messageCount = 5; + +// tag used with test messages +var tag1 = "istag"; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + // Get messageCount messages into the local filestore. + do_test_pending(); + + // function setupVirtualFolder() continues the testing after copyFileMessage. + MailServices.copy.copyFileMessage( + bugmail1, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + null + ); + return true; +} + +// nsIMsgCopyServiceListener implementation +var copyListener = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + hdrs.push(localAccountUtils.inboxFolder.GetMessageHeader(aKey)); + }, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + if (--messageCount) { + MailServices.copy.copyFileMessage( + bugmail1, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + null + ); + } else { + try { + setupVirtualFolder(); + } catch (ex) { + dump(ex); + } + } + }, +}; + +var virtualFolder; +var numTotalMessages; +var numUnreadMessages; + +// virtual folder setup +function setupVirtualFolder() { + // add as valid tag tag1, though probably not really necessary + MailServices.tags.addTagForKey(tag1, tag1, null, null); + + // add tag1 to 4 messages + let messages0to3 = [hdrs[0], hdrs[1], hdrs[2], hdrs[3]]; + localAccountUtils.inboxFolder.addKeywordsToMessages(messages0to3, tag1); + + // set 3 messages unread, 2 messages read + let messages0to2 = [hdrs[0], hdrs[1], hdrs[2]]; + localAccountUtils.inboxFolder.markMessagesRead(messages0to2, false); + + let messages3to4 = [hdrs[3], hdrs[4]]; + localAccountUtils.inboxFolder.markMessagesRead(messages3to4, true); + + // search will look for tag tag1 in the inbox folder + var searchTerm = makeSearchTerm( + localAccountUtils.inboxFolder, + tag1, + Ci.nsMsgSearchAttrib.Keywords, + Ci.nsMsgSearchOp.Contains + ); + + dump("creating virtual folder\n"); + var rootFolder = localAccountUtils.incomingServer.rootMsgFolder; + virtualFolder = CreateVirtualFolder( + "VfTest", + rootFolder, + localAccountUtils.inboxFolder.URI, + searchTerm, + false + ); + + // Setup search session. Execution continues with testVirtualFolder() + // after search is done. + + var searchSession = Cc[ + "@mozilla.org/messenger/searchSession;1" + ].createInstance(Ci.nsIMsgSearchSession); + searchSession.addScopeTerm( + Ci.nsMsgSearchScope.offlineMail, + localAccountUtils.inboxFolder + ); + searchSession.appendTerm(searchTerm, false); + searchSession.registerListener(searchListener); + dump("starting search of vf\n"); + searchSession.search(null); +} + +// partially based on gSearchNotificationListener in searchBar.js +// nsIMsgSearchNotify implementation +var searchListener = { + onNewSearch() { + dump("in onnewsearch\n"); + numTotalMessages = 0; + numUnreadMessages = 0; + }, + onSearchHit(dbHdr, folder) { + print("Search hit, isRead is " + dbHdr.isRead); + numTotalMessages++; + if (!dbHdr.isRead) { + numUnreadMessages++; + } + }, + onSearchDone(status) { + print("Finished search hitCount = " + numTotalMessages); + var db = virtualFolder.msgDatabase; + var dbFolderInfo = db.dBFolderInfo; + dbFolderInfo.numMessages = numTotalMessages; + dbFolderInfo.numUnreadMessages = numUnreadMessages; + virtualFolder.updateSummaryTotals(true); + print("virtual folder unread is " + virtualFolder.getNumUnread(false)); + testVirtualFolder(); + }, +}; + +function testVirtualFolder() { + // basic functionality tests + + // total messages matching search + Assert.equal(4, virtualFolder.getTotalMessages(false)); + + // total unread messages in search + Assert.equal(3, virtualFolder.getNumUnread(false)); + + // change unread of one item in search to decrease count + localAccountUtils.inboxFolder.markMessagesRead([hdrs[0]], true); + virtualFolder.updateSummaryTotals(true); + + Assert.equal(2, virtualFolder.getNumUnread(false)); + + // failures fixed in this bug + + // remove tag from one item to decrease count + var message1 = [hdrs[1]]; + localAccountUtils.inboxFolder.removeKeywordsFromMessages(message1, tag1); + virtualFolder.updateSummaryTotals(true); + Assert.equal(3, virtualFolder.getTotalMessages(false)); + Assert.equal(1, virtualFolder.getNumUnread(false)); + + // End of test, so release our header references + hdrs = null; + + do_test_finished(); + return true; +} + +// helper functions + +// adapted from commandglue.js +function CreateVirtualFolder( + newName, + parentFolder, + searchFolderURIs, + searchTerm, + searchOnline +) { + var newFolder = parentFolder.addSubfolder(newName); + newFolder.setFlag(Ci.nsMsgFolderFlags.Virtual); + var vfdb = newFolder.msgDatabase; + var searchTermString = getSearchTermString(searchTerm); + + var dbFolderInfo = vfdb.dBFolderInfo; + // set the view string as a property of the db folder info + // set the original folder name as well. + dbFolderInfo.setCharProperty("searchStr", searchTermString); + dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs); + dbFolderInfo.setBooleanProperty("searchOnline", searchOnline); + // This fails because the folder doesn't exist - why were we doing it? + // vfdb.summaryValid = true; + vfdb.close(true); + // use acctMgr to setup the virtual folder listener + var acctMgr = MailServices.accounts.QueryInterface(Ci.nsIFolderListener); + // print(acctMgr); + acctMgr.onFolderAdded(parentFolder, newFolder); + return newFolder; +} + +function getSearchTermString(term) { + var condition = ""; + + if (condition.length > 1) { + condition += " "; + } + + if (term.matchAll) { + condition = "ALL"; + } + condition += term.booleanAnd ? "AND (" : "OR ("; + condition += term.termAsString + ")"; + return condition; +} + +// Create a search term for searching aFolder +// using aAttrib, aOp, and string aStrValue +function makeSearchTerm(aFolder, aStrValue, aAttrib, aOp) { + // use a temporary search session + var searchSession = Cc[ + "@mozilla.org/messenger/searchSession;1" + ].createInstance(Ci.nsIMsgSearchSession); + searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail, aFolder); + var searchTerm = searchSession.createTerm(); + var value = searchTerm.value; + value.str = aStrValue; + searchTerm.value = value; + searchTerm.attrib = aAttrib; + searchTerm.op = aOp; + searchTerm.booleanAnd = false; + searchSession = null; + return searchTerm; +} diff --git a/comm/mailnews/base/test/unit/test_bug434810.js b/comm/mailnews/base/test/unit/test_bug434810.js new file mode 100644 index 0000000000..f41eea82d9 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_bug434810.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test of setup of localMailFolders + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + var rootFolder = localAccountUtils.incomingServer.rootFolder; + + var msgProps = Services.strings.createBundle( + "chrome://messenger/locale/messenger.properties" + ); + + var expectedFolders = ["Inbox"]; // Inbox hard-coded in LocalAccountUtils.jsm + + // These two MailNews adds by default + expectedFolders.push(msgProps.GetStringFromName("outboxFolderName")); + expectedFolders.push(msgProps.GetStringFromName("trashFolderName")); + + Assert.equal(rootFolder.numSubFolders, expectedFolders.length); + for (var i = 0; i < expectedFolders.length; ++i) { + Assert.ok(rootFolder.containsChildNamed(expectedFolders[i])); + } + Assert.ok(rootFolder.isAncestorOf(localAccountUtils.inboxFolder)); +} diff --git a/comm/mailnews/base/test/unit/test_bug471682.js b/comm/mailnews/base/test/unit/test_bug471682.js new file mode 100644 index 0000000000..73e431d84f --- /dev/null +++ b/comm/mailnews/base/test/unit/test_bug471682.js @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test of message database validity on local copy from bug 471682. What + * we want to do here is to copy a couple of message to a new folder, and + * then compare the date and filesize of the folder file with the + * stored result in dbfolderinfo. If they don't match, that's bad. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var bugmail1 = do_get_file("../../../data/bugmail1"); +var gHdr; // header of test message in local folder + +localAccountUtils.loadLocalMailAccount(); +// create a subfolder as a target for copies +var gSubfolder = + localAccountUtils.inboxFolder.createLocalSubfolder("subfolder"); + +function run_test() { + // make sure we're using berkeley mailbox format here since this test + // assumes berkeley mailbox format. + if ( + Services.prefs.getCharPref("mail.serverDefaultStoreContractID") != + "@mozilla.org/msgstore/berkeleystore;1" + ) { + return; + } + + do_test_pending(); + // step 1: copy a message into the local inbox + MailServices.copy.copyFileMessage( + bugmail1, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + step2, + null + ); +} + +// step 2: copy one message into a subfolder to establish an +// mbox file time and size +// nsIMsgCopyServiceListener implementation +var step2 = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + dump("in set message key\n"); + gHdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey); + }, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + Assert.notEqual(gHdr, null); + // copy the message into the subfolder + MailServices.copy.copyMessages( + localAccountUtils.inboxFolder, + [gHdr], + gSubfolder, + false, + step3, + null, + false + ); + }, +}; + +// step 3: after the copy, delay to allow copy to complete and allow possible +// file error time +// nsIMsgCopyServiceListener implementation +var step3 = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + do_timeout(2000, step4); + }, +}; + +// step 4: start a second copy +function step4() { + MailServices.copy.copyMessages( + localAccountUtils.inboxFolder, + [gHdr], + gSubfolder, + false, + step5, + null, + false + ); +} + +// step 5: actual tests of file size and date +// nsIMsgCopyServiceListener implementation +var step5 = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + var dbSize = gSubfolder.msgDatabase.dBFolderInfo.folderSize; + var dbDate = gSubfolder.msgDatabase.dBFolderInfo.folderDate; + var filePath = gSubfolder.filePath; + var date = parseInt(filePath.lastModifiedTime / 1000); + var size = filePath.fileSize; + Assert.equal(size, dbSize); + Assert.equal(date, dbDate); + // End of test, so release our header reference + gHdr = null; + do_test_finished(); + }, +}; diff --git a/comm/mailnews/base/test/unit/test_bug514945.js b/comm/mailnews/base/test/unit/test_bug514945.js new file mode 100644 index 0000000000..90ec8084dd --- /dev/null +++ b/comm/mailnews/base/test/unit/test_bug514945.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Testing of GetAvailable crashes in bug 514945 + */ + +function run_test() { + const kValidityManager = Cc[ + "@mozilla.org/mail/search/validityManager;1" + ].getService(Ci.nsIMsgSearchValidityManager); + + let validityTable = kValidityManager.getTable( + Ci.nsMsgSearchScope.offlineMail + ); + + // When we try to access a bad value of getAvailable, it should give an error, + // not crash. + let BAD_VALUE = 1000000; // some large value that is beyond the array bounds + let haveExpectedError = false; + try { + validityTable.getAvailable(Ci.nsMsgSearchAttrib.Subject, BAD_VALUE); + } catch (e) { + dump("Error but no crash, this is what we want:" + e + "\n"); + haveExpectedError = true; + } + + Assert.ok(haveExpectedError); + + // One of the causes of this is that search term operators are not being + // initialized, resulting in random values of the operator. Make sure that is + // fixed. + + const kSearchSession = Cc[ + "@mozilla.org/messenger/searchSession;1" + ].createInstance(Ci.nsIMsgSearchSession); + let searchTerm = kSearchSession.createTerm(); + Assert.equal(searchTerm.op, Ci.nsMsgSearchOp.Contains); +} diff --git a/comm/mailnews/base/test/unit/test_closedDB.js b/comm/mailnews/base/test/unit/test_closedDB.js new file mode 100644 index 0000000000..fd752bf81b --- /dev/null +++ b/comm/mailnews/base/test/unit/test_closedDB.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests that an nsMsgDBFolder's database connection and listeners are + * restored when called by certain functions. + */ + +const { MessageGenerator } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); + +add_task(async function () { + // Create a folder and some messages. + + let generator = new MessageGenerator(); + + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.accounts[0]; + account.addIdentity(MailServices.accounts.createIdentity()); + + let rootFolder = account.incomingServer.rootFolder; + rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + + let testFolder = rootFolder.createLocalSubfolder("testFolder"); + testFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + testFolder.addMessageBatch( + generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) + ); + let testMessages = [...testFolder.messages]; + + // Listen for notifications. + + let folderListener = { + QueryInterface: ChromeUtils.generateQI(["nsIFolderListener"]), + notifications: [], + onFolderIntPropertyChanged(folder, property, oldValue, newValue) { + if (property != "TotalUnreadMessages") { + return; + } + + this.notifications.push({ folder, property, oldValue, newValue }); + }, + consumeNotification(expectedFolder, expectedOldValue, expectedNewValue) { + let { folder, oldValue, newValue } = this.notifications.shift(); + Assert.equal(folder, expectedFolder, "notification folder"); + Assert.equal(oldValue, expectedOldValue, "notification oldValue"); + Assert.equal(newValue, expectedNewValue, "notification newValue"); + }, + }; + MailServices.mailSession.AddFolderListener( + folderListener, + Ci.nsIFolderListener.intPropertyChanged + ); + + // Clear the database reference, then mark some messages as read. We should + // see the unread count change and get two notifications about it. We could + // check `testFolder.msgDatabase` is not null afterwards, but that would be + // pointless, because the getter restores the database. + + testFolder.msgDatabase = null; + testFolder.markMessagesRead([testMessages[0], testMessages[4]], true); + Assert.equal( + testFolder.getNumUnread(false), + 3, + "unread message count should be updated" + ); + Assert.equal( + folderListener.notifications.length, + 2, + "two folder notifications should have fired" + ); + folderListener.consumeNotification(testFolder, 5, 4); + folderListener.consumeNotification(testFolder, 4, 3); + + // Clear the database reference, then mark some messages as flagged. This + // doesn't prove much except that nothing exploded. + + testFolder.msgDatabase = null; + testFolder.markMessagesFlagged([testMessages[1], testMessages[3]], true); + + // Clear the database reference, then mark all messages as read. We should + // see the unread count change to zero and get a notification about it. + + testFolder.msgDatabase = null; + testFolder.markAllMessagesRead(null); + Assert.equal( + testFolder.getNumUnread(false), + 0, + "unread message count should be updated" + ); + Assert.equal( + folderListener.notifications.length, + 1, + "a folder notifications should have fired" + ); + folderListener.consumeNotification(testFolder, 3, 0); + + // Clean up. + + MailServices.mailSession.RemoveFolderListener(folderListener); + MailServices.accounts.removeAccount(account, false); +}); diff --git a/comm/mailnews/base/test/unit/test_compactFailure.js b/comm/mailnews/base/test/unit/test_compactFailure.js new file mode 100644 index 0000000000..300c8abda7 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_compactFailure.js @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); + +/* import-globals-from ../../../test/resources/logHelper.js */ +load("../../../resources/logHelper.js"); +var { addMessagesToFolder, MessageGenerator, MessageScenarioFactory } = + ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm"); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +var gTargetFolder; +var gCid; + +// Allow certain xpcom errors. +logHelperAllowedErrors.push("NS_ERROR_FILE_NOT_FOUND"); + +var MsgDBServiceFailure = { + QueryInterface: ChromeUtils.generateQI(["nsIMsgDBService"]), + + openMailDBFromFile(file, folder, create, leaveInvalidDB) { + if (folder.name == "ShouldFail") { + throw Components.Exception("", Cr.NS_ERROR_FILE_NOT_FOUND); + } + return this._genuine.openMailDBFromFile( + file, + folder, + create, + leaveInvalidDB + ); + }, + + openFolderDB(folder, leaveInvalidDB) { + return this._genuine.openFolderDB(folder, leaveInvalidDB); + }, + createNewDB(folder) { + return this._genuine.createNewDB(folder); + }, + registerPendingListener(folder, listener) { + this._genuine.registerPendingListener(folder, listener); + }, + unregisterPendingListener(listener) { + this._genuine.unregisterPendingListener(listener); + }, + cachedDBForFolder(folder) { + return this._genuine.cachedDBFolder(folder); + }, + get openDBs() { + return this._genuine.openDBs; + }, +}; + +function generate_messages() { + let messageGenerator = new MessageGenerator(); + let scenarioFactory = new MessageScenarioFactory(messageGenerator); + let messages = []; + messages = messages.concat(scenarioFactory.directReply(10)); + return messages; +} + +async function compact_with_exception(expectedException) { + let compactor = Cc["@mozilla.org/messenger/foldercompactor;1"].createInstance( + Ci.nsIMsgFolderCompactor + ); + let listener = new PromiseTestUtils.PromiseUrlListener(); + compactor.compactFolders([gTargetFolder], listener, null); + try { + await listener.promise; + do_throw( + "nsIMsgFolderCompactor listener wasn't called with a failure code." + ); + } catch (failureCode) { + Assert.equal(expectedException, failureCode); + } +} + +function create_local_folders() { + let rootFolder = localAccountUtils.rootFolder; + let localTrashFolder = rootFolder.getChildNamed("Trash"); + localTrashFolder.setFlag(Ci.nsMsgFolderFlags.Trash); +} + +async function delete_all_messages() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + gTargetFolder.deleteMessages( + [...gTargetFolder.messages], + null, + false, // Do not delete storage. + true, // Is a move. + promiseCopyListener, + false // Do not allow undo, currently leaks. + ); + await promiseCopyListener.promise; +} + +add_setup(function () { + localAccountUtils.loadLocalMailAccount(); + create_local_folders(); +}); + +add_task(async function test_compact_without_failure() { + // Setup open failure folder. + gTargetFolder = + localAccountUtils.rootFolder.createLocalSubfolder("ShouldFail"); + addMessagesToFolder(generate_messages(), gTargetFolder); + + await new Promise(resolve => { + mailTestUtils.updateFolderAndNotify(gTargetFolder, resolve); + }); + // Delete messages. + await delete_all_messages(); + // Setup db service mock. + gCid = MockRegistrar.register( + "@mozilla.org/msgDatabase/msgDBService;1", + MsgDBServiceFailure + ); + // Test compact without failure. + await compact_with_exception(Cr.NS_ERROR_FILE_NOT_FOUND); + // Teardown db service mock. + MockRegistrar.unregister(gCid); +}); diff --git a/comm/mailnews/base/test/unit/test_converterDeferredAccount.js b/comm/mailnews/base/test/unit/test_converterDeferredAccount.js new file mode 100644 index 0000000000..effc6d57e2 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_converterDeferredAccount.js @@ -0,0 +1,206 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); +var { convertMailStoreTo } = ChromeUtils.import( + "resource:///modules/mailstoreConverter.jsm" +); +const { FolderUtils } = ChromeUtils.import( + "resource:///modules/FolderUtils.jsm" +); + +// XXX: merge into test_converter.js + +var log = console.createInstance({ + prefix: "mail.mailstoreconverter", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.mailstoreconverter.loglevel", +}); + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +// No. of messages/files and folders copied. +var gMsgHdrs = []; +// {nsIMsgLocalMailFolder} folder carrying messages for the pop server. +var gInbox; +// {nsIMsgIncomingServer} server for first deferred pop account. +var gServer1; +// {nsIMsgIncomingServer} server for second deferred pop account. +var gServer2; +// {nsIMsgIncomingServer} server to convert. +var gServer; + +var copyListenerWrap = { + SetMessageKey(aKey) { + let hdr = gInbox.GetMessageHeader(aKey); + gMsgHdrs.push({ hdr, ID: hdr.messageId }); + }, + OnStopCopy(aStatus) { + // Check: message successfully copied. + Assert.equal(aStatus, 0); + }, +}; + +var EventTarget = function () { + this.dispatchEvent = function (event) { + if (event.type == "progress") { + log.trace("Progress: " + event.detail); + } + }; +}; + +function copyFileMessage(file, destFolder, isDraftOrTemplate) { + let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap); + MailServices.copy.copyFileMessage( + file, + destFolder, + null, + isDraftOrTemplate, + 0, + "", + listener, + null + ); + return listener.promise; +} + +/** + * Check that conversion worked for the given source. + * + * @param {nsIFile} source - mbox source directory. + * @param {nsIFile} target - maildir target directory. + */ +function checkConversion(source, target) { + for (let sourceContent of source.directoryEntries) { + let sourceContentName = sourceContent.leafName; + let ext = sourceContentName.substr(-4); + let targetFile = FileUtils.File( + PathUtils.join(target.path, sourceContentName) + ); + log.debug("Checking path: " + targetFile.path); + if (ext == ".dat") { + Assert.ok(targetFile.exists()); + } else if (sourceContent.isDirectory()) { + Assert.ok(targetFile.exists()); + checkConversion(sourceContent, targetFile); + } else if (ext != ".msf") { + Assert.ok(targetFile.exists()); + let cur = FileUtils.File(PathUtils.join(targetFile.path, "cur")); + Assert.ok(cur.exists()); + let tmp = FileUtils.File(PathUtils.join(targetFile.path, "tmp")); + Assert.ok(tmp.exists()); + if (targetFile.leafName == "Inbox") { + let curContents = cur.directoryEntries; + let curContentsCount = [...curContents].length; + Assert.equal(curContentsCount, 1000); + } + } + } +} + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + // Set up two deferred pop accounts. + gServer1 = MailServices.accounts.createIncomingServer( + "test1", + "localhost1", + "pop3" + ); + gServer2 = MailServices.accounts.createIncomingServer( + "test2", + "localhost2", + "pop3" + ); + var accountPop1 = MailServices.accounts.createAccount(); + var accountPop2 = MailServices.accounts.createAccount(); + + // Set incoming servers. + accountPop1.incomingServer = gServer1; + gServer1.QueryInterface(Ci.nsIPop3IncomingServer); + gServer1.valid = true; + accountPop2.incomingServer = gServer2; + gServer2.QueryInterface(Ci.nsIPop3IncomingServer); + gServer2.valid = true; + + // Defer accounts to Local Folders. + gServer1.deferredToAccount = localAccountUtils.msgAccount.key; + gServer2.deferredToAccount = localAccountUtils.msgAccount.key; + + // 'gServer1' should be deferred. Get the path of the root folder to which + // other accounts are deferred. + ok(gServer1.rootFolder.filePath.path != gServer1.rootMsgFolder.filePath.path); + let deferredToRootFolder = gServer1.rootMsgFolder.filePath.path; + + // Account to which other accounts have been deferred. + let deferredToAccount; + // String to hold names of accounts to convert. + let accountsToConvert = ""; + + let accounts = FolderUtils.allAccountsSorted(true); + for (let account of accounts) { + if ( + account.incomingServer.rootFolder.filePath.path == deferredToRootFolder + ) { + // Other accounts may be deferred to this account. + deferredToAccount = account; + } else if ( + account.incomingServer.rootMsgFolder.filePath.path == deferredToRootFolder + ) { + // This is a deferred account. + accountsToConvert += account.incomingServer.username + ", "; + } + } + + accountsToConvert = + accountsToConvert + deferredToAccount.incomingServer.username; + log.info(accountsToConvert + " will be converted"); + + gInbox = localAccountUtils.inboxFolder; + gServer = deferredToAccount.incomingServer; + + run_next_test(); +} + +add_setup(async function () { + let msgFile = do_get_file("../../../data/bugmail10"); + // Add 1000 messages to the "Inbox" folder. + for (let i = 0; i < 1000; i++) { + await copyFileMessage(msgFile, gInbox, false); + } +}); + +add_task(function testMaildirConversion() { + let mailstoreContractId = Services.prefs.getCharPref( + "mail.server." + gServer.key + ".storeContractID" + ); + + do_test_pending(); + let pConverted = convertMailStoreTo( + mailstoreContractId, + gServer, + new EventTarget() + ); + let originalRootFolder = gServer.rootFolder.filePath; + + pConverted + .then(function (val) { + log.debug("Conversion done: " + originalRootFolder.path + " => " + val); + let newRootFolder = gServer.rootFolder.filePath; + checkConversion(originalRootFolder, newRootFolder); + do_test_finished(); + }) + .catch(function (reason) { + log.error("Conversion failed: " + reason.error); + ok(false); // Fail the test! + }); +}); diff --git a/comm/mailnews/base/test/unit/test_copyChaining.js b/comm/mailnews/base/test/unit/test_copyChaining.js new file mode 100644 index 0000000000..b014049ed3 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_copyChaining.js @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test of chaining copies between the same folders + +/* import-globals-from ../../../test/resources/MessageGenerator.jsm */ +load("../../../resources/MessageGenerator.jsm"); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gCopySource; +var gCopyDest; +var gMessages; +var gCurTestNum = 1; + +// main test + +var gTestArray = [ + function copyMsg1() { + gMessages = [...gCopySource.msgDatabase.enumerateMessages()]; + CopyNextMessage(); + }, + function copyMsg2() { + CopyNextMessage(); + }, + function copyMsg3() { + CopyNextMessage(); + }, + function copyMsg4() { + CopyNextMessage(); + }, +]; + +function CopyNextMessage() { + if (gMessages.length > 0) { + let msgHdr = gMessages.shift(); + MailServices.copy.copyMessages( + gCopySource, + [msgHdr], + gCopyDest, + true, + copyListener, + null, + false + ); + } else { + do_throw("TEST FAILED - out of messages"); + } +} + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + let messageGenerator = new MessageGenerator(); + let scenarioFactory = new MessageScenarioFactory(messageGenerator); + + // "Master" do_test_pending(), paired with a do_test_finished() at the end of + // all the operations. + do_test_pending(); + + gCopyDest = localAccountUtils.inboxFolder.createLocalSubfolder("copyDest"); + // build up a diverse list of messages + let messages = []; + messages = messages.concat(scenarioFactory.directReply(10)); + gCopySource = localAccountUtils.rootFolder.createLocalSubfolder("copySource"); + addMessagesToFolder(messages, gCopySource); + + mailTestUtils.updateFolderAndNotify(gCopySource, doTest); + return true; +} + +function doTest() { + var test = gCurTestNum; + if (test <= gTestArray.length) { + var testFn = gTestArray[test - 1]; + dump("Doing test " + test + " " + testFn.name + "\n"); + + try { + testFn(); + } catch (ex) { + do_throw("TEST FAILED " + ex); + } + } else { + endTest(); + } +} + +function endTest() { + // Cleanup, null out everything + dump(" Exiting mail tests\n"); + gMessages = null; + do_test_finished(); // for the one in run_test() +} + +// nsIMsgCopyServiceListener implementation +var copyListener = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + // Check: message successfully copied. + Assert.equal(aStatus, 0); + ++gCurTestNum; + doTest(); + }, +}; diff --git a/comm/mailnews/base/test/unit/test_copyToInvalidDB.js b/comm/mailnews/base/test/unit/test_copyToInvalidDB.js new file mode 100644 index 0000000000..2c0dd85d9f --- /dev/null +++ b/comm/mailnews/base/test/unit/test_copyToInvalidDB.js @@ -0,0 +1,106 @@ +/* + * Simple tests for copying local messages to a folder whose db is missing + * or invalid + */ + +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +const { MessageGenerator } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); + +async function setup() { + let createSubfolder = async function (parentFolder, name) { + let promiseAdded = PromiseTestUtils.promiseFolderAdded(name); + parentFolder.createSubfolder(name, null); + await promiseAdded; + return parentFolder.getChildNamed(name); + }; + + // Create account. + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.FindAccountForServer( + MailServices.accounts.localFoldersServer + ); + let root = account.incomingServer.rootFolder; + + // Add a couple of folders containing some test messages. + let folder1 = await createSubfolder(root, "test1"); + folder1.QueryInterface(Ci.nsIMsgLocalMailFolder); + + let folder2 = await createSubfolder(root, "test2"); + folder2.QueryInterface(Ci.nsIMsgLocalMailFolder); + + let gen = new MessageGenerator(); + let msg1 = gen.makeMessage(); + let msg2 = gen.makeMessage({ inReplyTo: msg1 }); + folder1.addMessageBatch([msg1, msg2].map(m => m.toMboxString())); + + let msg3 = gen.makeMessage(); + folder2.addMessage(msg3.toMboxString()); + + return [folder1, folder2]; +} + +add_task(async function test_copyToInvalidDB() { + let [folder1, folder2] = await setup(); + + // folder1 contains [msg1, msg2]. + // folder2 contains [msg3]. + + // Take note of the message we're going to move (first msg in folder1). + let msgHdr = Array.from(folder1.msgDatabase.enumerateMessages())[0]; + let expectedID = msgHdr.messageId; + let expectedMsg = mailTestUtils.loadMessageToString(folder1, msgHdr); + + // Sabotage the destination folder2 database. + folder2.msgDatabase.summaryValid = false; + folder2.msgDatabase = null; + folder2.ForceDBClosed(); + // In fact, delete the .msf file entirely. + folder2.summaryFile.remove(false); + + // So folder2 has no trace of a DB. + Assert.equal(folder2.databaseOpen, false); + Assert.equal(folder2.summaryFile.exists(), false); + + // Move the message from folder1 to folder2. + let copyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyMessages( + folder1, + [msgHdr], + folder2, + true, // isMove + copyListener, + null, // window + false // allowUndo + ); + await copyListener.promise; + + // Current behaviour: + // After the move, there's still no sign of a DB file. + // Yet the copy didn't fail (see Bug 1737203). + Assert.equal(folder2.databaseOpen, false); + Assert.equal(folder2.summaryFile.exists(), false); + + // Rebuild the the database. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + try { + folder2.getDatabaseWithReparse(urlListener, null); + } catch (ex) { + // We expect this - it indicates the DB is not valid. But it will have + // kicked off an async reparse, so we need to wait for the listener. + Assert.equal(ex.result, Cr.NS_ERROR_NOT_INITIALIZED); + await urlListener.promise; + } + + // Check that the message moved over intact. + let gotHdr = folder2.msgDatabase.getMsgHdrForMessageID(expectedID); + let gotMsg = mailTestUtils.loadMessageToString(folder2, gotHdr); + // NOTE: With maildir store, the message seems to gain an extra trailing + // "\n" during the copy. See Bug 1716651. + // For now, use .trim() as a workaround. + Assert.equal(gotMsg.trim(), expectedMsg.trim()); +}); diff --git a/comm/mailnews/base/test/unit/test_detachToFile.js b/comm/mailnews/base/test/unit/test_detachToFile.js new file mode 100644 index 0000000000..3c1913a9a8 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_detachToFile.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests nsIMessenger's detachAttachmentsWOPrompts + */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MsgHdrToMimeMessage } = ChromeUtils.import( + "resource:///modules/gloda/MimeMessage.jsm" +); + +function SaveAttachmentCallback() { + this.attachments = null; + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); +} + +SaveAttachmentCallback.prototype = { + callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) { + this.attachments = aMimeMessage.allAttachments; + this._resolve(); + }, + get promise() { + return this._promise; + }, +}; +var gCallbackObject = new SaveAttachmentCallback(); + +add_setup(async function () { + if (!localAccountUtils.inboxFolder) { + localAccountUtils.loadLocalMailAccount(); + } +}); + +add_task(async function startCopy() { + // Get a message into the local filestore. + let mailFile = do_get_file("../../../data/external-attach-test"); + let listener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + mailFile, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + listener, + null + ); + await listener.promise; +}); + +// process the message through mime +add_task(async function startMime() { + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + + MsgHdrToMimeMessage( + msgHdr, + gCallbackObject, + gCallbackObject.callback, + true // allowDownload + ); + + await gCallbackObject.promise; +}); + +// detach any found attachments +add_task(async function startDetach() { + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey); + + let messenger = Cc["@mozilla.org/messenger;1"].createInstance( + Ci.nsIMessenger + ); + let attachment = gCallbackObject.attachments[0]; + let listener = new PromiseTestUtils.PromiseUrlListener(); + + messenger.detachAttachmentsWOPrompts( + do_get_profile(), + [attachment.contentType], + [attachment.url], + [attachment.name], + [msgURI], + listener + ); + await listener.promise; +}); + +/** + * Test that the detachment was successful. + */ +add_task(async function testDetach() { + // The message contained a file "check.pdf" which should + // now exist in the profile directory. + let checkFile = do_get_profile().clone(); + checkFile.append("check.pdf"); + Assert.ok(checkFile.exists()); + + // The message should now have a detached attachment. Read the message, + // and search for "AttachmentDetached" which is added on detachment. + + // Get the message header + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + + let messageContent = await getContentFromMessage(msgHdr); + Assert.ok(messageContent.includes("AttachmentDetached")); +}); + +/** + * Get the full message content. + * + * @param {nsIMsgDBHdr} aMsgHdr - Message whose text body will be read. + * @returns {Promise<string>} full message contents. + */ +function getContentFromMessage(aMsgHdr) { + let msgFolder = aMsgHdr.folder; + let msgUri = msgFolder.getUriForMsg(aMsgHdr); + + return new Promise((resolve, reject) => { + let streamListener = { + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ), + content: "", + onDataAvailable(request, inputStream, offset, count) { + this.sis.init(inputStream); + this.content += this.sis.read(count); + }, + onStartRequest(request) {}, + onStopRequest(request, statusCode) { + this.sis.close(); + if (Components.isSuccessCode(statusCode)) { + resolve(this.content); + } else { + reject(new Error(statusCode)); + } + }, + }; + MailServices.messageServiceFromURI(msgUri).streamMessage( + msgUri, + streamListener, + null, + null, + false, + "", + false + ); + }); +} diff --git a/comm/mailnews/base/test/unit/test_emptyTrash.js b/comm/mailnews/base/test/unit/test_emptyTrash.js new file mode 100644 index 0000000000..528691117e --- /dev/null +++ b/comm/mailnews/base/test/unit/test_emptyTrash.js @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Test suite for empty trash + * + * Currently tested: + * - Empty local trash + * TODO + * - Empty imap trash + */ + +// Globals +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gMsgFile1; +var gLocalTrashFolder; +var gCurTestNum; +var gMsgHdrs = []; +var gRootFolder; + +var nsIMFNService = Ci.nsIMsgFolderNotificationService; + +// nsIMsgCopyServiceListener implementation +var copyListener = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + let hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey); + gMsgHdrs.push({ hdr, ID: hdr.messageId }); + }, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + // Check: message successfully copied. + Assert.equal(aStatus, 0); + // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack + // This can happen with a bunch of synchronous functions grouped together, and + // can even cause tests to fail because they're still waiting for the listener + // to return + do_timeout(0, function () { + doTest(++gCurTestNum); + }); + }, +}; + +var urlListener = { + OnStartRunningUrl(aUrl) {}, + OnStopRunningUrl(aUrl, aExitCode) { + // Check: message successfully copied. + Assert.equal(aExitCode, 0); + // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack + // This can happen with a bunch of synchronous functions grouped together, and + // can even cause tests to fail because they're still waiting for the listener + // to return + do_timeout(0, function () { + doTest(++gCurTestNum); + }); + }, +}; + +function copyFileMessage(file, destFolder, isDraftOrTemplate) { + MailServices.copy.copyFileMessage( + file, + destFolder, + null, + isDraftOrTemplate, + 0, + "", + copyListener, + null + ); +} + +function deleteMessages(srcFolder, items) { + srcFolder.deleteMessages(items, null, false, true, copyListener, true); +} + +/* + * TESTS + */ + +// Beware before commenting out a test -- later tests might just depend on earlier ones +var gTestArray = [ + // Copying message from file + function testCopyFileMessage1() { + copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false); + }, + + // Delete message + function testDeleteMessage() { + // delete to trash + // Let's take a moment to re-initialize stuff that got moved + let inboxDB = localAccountUtils.inboxFolder.msgDatabase; + gMsgHdrs[0].hdr = inboxDB.getMsgHdrForMessageID(gMsgHdrs[0].ID); + + // Now delete the message + deleteMessages(localAccountUtils.inboxFolder, [gMsgHdrs[0].hdr]); + }, + function emptyTrash() { + gRootFolder = localAccountUtils.incomingServer.rootMsgFolder; + gLocalTrashFolder = gRootFolder.getChildNamed("Trash"); + // hold onto a db to make sure that empty trash deals with the case + // of someone holding onto the db, but the trash folder has a null db. + let gLocalTrashDB = gLocalTrashFolder.msgDatabase; // eslint-disable-line no-unused-vars + gLocalTrashFolder.msgDatabase = null; + // this is synchronous + gLocalTrashFolder.emptyTrash(null); + // check that the trash folder is 0 size, that the db has a 0 message count + // and has no messages. + Assert.equal(0, gLocalTrashFolder.filePath.fileSize); + Assert.equal(0, gLocalTrashFolder.msgDatabase.dBFolderInfo.numMessages); + let msgs = [...gLocalTrashFolder.msgDatabase.enumerateMessages()]; + Assert.equal(0, msgs.length); + urlListener.OnStopRunningUrl(null, 0); + }, +]; + +// Our listener, which captures events. +function gMFListener() {} +gMFListener.prototype = { + folderDeleted(aFolder) { + aFolder.msgDatabase = null; + }, +}; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + // Load up a message so that we can copy it in later. + gMsgFile1 = do_get_file("../../../data/bugmail10"); + // our front end code clears the msg db when it gets told the folder for + // an open view has been deleted - so simulate that. + var folderDeletedListener = new gMFListener(); + MailServices.mfn.addListener( + folderDeletedListener, + nsIMFNService.folderDeleted + ); + + // "Master" do_test_pending(), paired with a do_test_finished() at the end of all the operations. + do_test_pending(); + + // Do the test. + doTest(1); +} + +function doTest(test) { + if (test <= gTestArray.length) { + gCurTestNum = test; + + var testFn = gTestArray[test - 1]; + // Set a limit of three seconds; if the notifications haven't arrived by then there's a problem. + do_timeout(10000, function () { + if (gCurTestNum == test) { + do_throw( + "Notifications not received in 10000 ms for operation " + testFn.name + ); + } + }); + try { + testFn(); + } catch (ex) { + dump(ex); + } + } else { + gMsgHdrs = null; + do_test_finished(); // for the one in run_test() + } +} diff --git a/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js b/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js new file mode 100644 index 0000000000..0fd09a543a --- /dev/null +++ b/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that we cleanup the account prefs when a pop3 account has + * been deferred to a hidden account. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create account prefs with a pop3 account deferred to a hidden account. + + Services.prefs.setCharPref("mail.account.account1.identities", "id1"); + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.account.account4.identities", "id2"); + Services.prefs.setCharPref("mail.account.account4.server", "server4"); + Services.prefs.setCharPref("mail.account.account5.identities", "id3"); + Services.prefs.setCharPref("mail.account.account5.server", "server5"); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server2.hostname", "Smart Mailboxes"); + Services.prefs.setCharPref("mail.server.server2.type", "none"); + Services.prefs.setBoolPref("mail.server.server2.hidden", true); + Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org"); + Services.prefs.setCharPref("mail.server.server4.type", "pop3"); + Services.prefs.setCharPref( + "mail.server.server4.deferred_to_account", + "account2" + ); + Services.prefs.setCharPref("mail.server.server5.hostname", "mail.host5.org"); + Services.prefs.setCharPref("mail.server.server5.type", "pop3"); + Services.prefs.setCharPref( + "mail.server.server5.deferred_to_account", + "account2" + ); + + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account1,account2,account4,account5" + ); + // Set the default account to one we're going to get rid of. The account manager + // should recover relatively gracefully. + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1"); + + // This will force the load of the accounts setup above. + Assert.equal(MailServices.accounts.accounts.length, 3); // hidden account not included + let server4 = MailServices.accounts + .getAccount("account4") + .incomingServer.QueryInterface(Ci.nsIPop3IncomingServer); + Assert.equal(server4.deferredToAccount, "account1"); + let server5 = MailServices.accounts + .getAccount("account5") + .incomingServer.QueryInterface(Ci.nsIPop3IncomingServer); + Assert.equal(server5.deferredToAccount, "account1"); +} diff --git a/comm/mailnews/base/test/unit/test_folderCompact.js b/comm/mailnews/base/test/unit/test_folderCompact.js new file mode 100644 index 0000000000..876672c0e5 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_folderCompact.js @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Test suite for folder compaction + * + * Currently tested: + * - Compacting local folders + * TODO + * - Compacting imap offline stores. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +// Globals +var gMsgFile1, gMsgFile2, gMsgFile3; +var gMsg2ID = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org"; +var gMsg3ID = "4849BF7B.2030800@example.com"; +var gLocalFolder2; +var gLocalFolder3; +// After a compact (or other operation), this is what we expect the +// folder size to be. +var gExpectedFolderSize; +var gMsgHdrs = []; +var gExpectedInboxSize; +var gExpectedFolder2Size; +var gExpectedFolder3Size; +// Use this to account for the size of the separating line after the message +// body. The compactor tries to preserve the convention already in the mbox, +// and we know that our test messages have CRLFs, so that's what we'll use. +var gSeparatorLine = "\r\n"; + +// Transfer message keys between function calls. +var gMsgKeys = []; + +// nsIMsgCopyServiceListener implementation +var copyListenerWrap = { + SetMessageKey(aKey) { + let hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey); + gMsgHdrs.push({ hdr, ID: hdr.messageId }); + }, + OnStopCopy(aStatus) { + // Check: message successfully copied. + Assert.equal(aStatus, 0); + }, +}; + +var urlListenerWrap = { + OnStopRunningUrl(aUrl, aExitCode) { + // Check: message successfully copied. + Assert.equal(aExitCode, 0); + + if (gMsgKeys.length > 0) { + // Bug 854798: Check if the new message keys are the same as before compaction. + let folderMsgs = [...gMsgKeys.folder.messages]; + // First message was deleted so skip it in the old array. + let expectedKeys = [...gMsgKeys].slice(1); + Assert.equal(folderMsgs.length, expectedKeys.length); + for (let i = 1; i < expectedKeys.length; i++) { + Assert.equal(folderMsgs[i], expectedKeys[i]); + } + gMsgKeys.length = 0; + } + }, +}; + +function copyFileMessage(file, destFolder, isDraftOrTemplate) { + let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap); + MailServices.copy.copyFileMessage( + file, + destFolder, + null, + isDraftOrTemplate, + 0, + "", + listener, + null + ); + return listener.promise; +} + +function copyMessages(items, isMove, srcFolder, destFolder) { + let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap); + MailServices.copy.copyMessages( + srcFolder, + items, + destFolder, + isMove, + listener, + null, + true + ); + return listener.promise; +} + +function deleteMessages(srcFolder, items) { + let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap); + srcFolder.deleteMessages(items, null, false, true, listener, true); + return listener.promise; +} + +function calculateFolderSize(folder) { + let msgDB = folder.msgDatabase; + let totalSize = 0; + for (let header of msgDB.enumerateMessages()) { + totalSize += header.messageSize + gSeparatorLine.length; + } + return totalSize; +} + +function verifyMsgOffsets(folder) { + let msgDB = folder.msgDatabase; + let enumerator = msgDB.enumerateMessages(); + if (enumerator) { + for (let header of enumerator) { + if (header instanceof Ci.nsIMsgDBHdr) { + let storeToken = header.getStringProperty("storeToken"); + Assert.equal(storeToken, header.messageOffset); + } + } + } +} + +/* + * TESTS + */ + +// Beware before commenting out a test -- later tests might just depend on earlier ones +var gTestArray = [ + // Copying messages from files + async function testCopyFileMessage1() { + await copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false); + }, + async function testCopyFileMessage2() { + await copyFileMessage(gMsgFile2, localAccountUtils.inboxFolder, false); + }, + async function testCopyFileMessage3() { + await copyFileMessage(gMsgFile3, localAccountUtils.inboxFolder, true); + showMessages( + localAccountUtils.inboxFolder, + "after initial 3 messages copy to inbox" + ); + }, + + // Moving/copying messages + async function testCopyMessages1() { + await copyMessages( + [gMsgHdrs[0].hdr], + false, + localAccountUtils.inboxFolder, + gLocalFolder2 + ); + }, + async function testCopyMessages2() { + await copyMessages( + [gMsgHdrs[1].hdr, gMsgHdrs[2].hdr], + false, + localAccountUtils.inboxFolder, + gLocalFolder2 + ); + showMessages(gLocalFolder2, "after copying 3 messages"); + }, + async function testMoveMessages1() { + await copyMessages( + [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr], + true, + localAccountUtils.inboxFolder, + gLocalFolder3 + ); + + showMessages(localAccountUtils.inboxFolder, "after moving 2 messages"); + showMessages(gLocalFolder3, "after moving 2 messages"); + }, + + // Deleting messages + async function testDeleteMessages1() { + // delete to trash + // Let's take a moment to re-initialize stuff that got moved + var folder3DB = gLocalFolder3.msgDatabase; + gMsgHdrs[0].hdr = folder3DB.getMsgHdrForMessageID(gMsgHdrs[0].ID); + + // Store message keys before deletion and compaction. + gMsgKeys.folder = gLocalFolder3; + for (let header of gLocalFolder3.messages) { + gMsgKeys.push(header.messageKey); + } + + // Now delete the message + await deleteMessages(gLocalFolder3, [gMsgHdrs[0].hdr]); + + showMessages(gLocalFolder3, "after deleting 1 message to trash"); + }, + async function compactFolder() { + gExpectedFolderSize = calculateFolderSize(gLocalFolder3); + Assert.notEqual(gLocalFolder3.expungedBytes, 0); + let listener = new PromiseTestUtils.PromiseUrlListener(urlListenerWrap); + gLocalFolder3.compact(listener, null); + await listener.promise; + + showMessages(gLocalFolder3, "after compact"); + }, + async function testDeleteMessages2() { + Assert.equal(gExpectedFolderSize, gLocalFolder3.filePath.fileSize); + verifyMsgOffsets(gLocalFolder3); + var folder2DB = gLocalFolder2.msgDatabase; + gMsgHdrs[0].hdr = folder2DB.getMsgHdrForMessageID(gMsgHdrs[0].ID); + + // Store message keys before deletion and compaction. + gMsgKeys.folder = gLocalFolder2; + for (let header of gLocalFolder2.messages) { + gMsgKeys.push(header.messageKey); + } + + // Now delete the message + await deleteMessages(gLocalFolder2, [gMsgHdrs[0].hdr]); + + showMessages(gLocalFolder2, "after deleting 1 message"); + }, + async function compactAllFolders() { + gExpectedInboxSize = calculateFolderSize(localAccountUtils.inboxFolder); + gExpectedFolder2Size = calculateFolderSize(gLocalFolder2); + gExpectedFolder3Size = calculateFolderSize(gLocalFolder3); + + // Save the first message key, which will change after compact with + // rebuild. + let f2m2Key = + gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID).messageKey; + + // force expunged bytes count to get cached. + gLocalFolder2.expungedBytes; + // mark localFolder2 as having an invalid db, and remove it + // for good measure. + gLocalFolder2.msgDatabase.summaryValid = false; + gLocalFolder2.msgDatabase = null; + gLocalFolder2.ForceDBClosed(); + let dbPath = gLocalFolder2.filePath; + dbPath.leafName = dbPath.leafName + ".msf"; + dbPath.remove(false); + + showMessages(localAccountUtils.inboxFolder, "before compactAll"); + // Save the key for the inbox message, we'll check after compact that it + // did not change. + let preInboxMsg3Key = + localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID( + gMsg3ID + ).messageKey; + + // We used to check here that the keys did not change during rebuild. + // But that is no true in general, it was only conicidental since the + // checked folder had never been compacted, so the key equaled the offset. + // We do not in guarantee that, indeed after rebuild we expect the keys + // to change. + let checkResult = { + OnStopRunningUrl(aUrl, aExitCode) { + // Check: message successfully compacted. + Assert.equal(aExitCode, 0); + }, + }; + let listener = new PromiseTestUtils.PromiseUrlListener(checkResult); + localAccountUtils.inboxFolder.compactAll(listener, null); + await listener.promise; + + showMessages(localAccountUtils.inboxFolder, "after compactAll"); + showMessages(gLocalFolder2, "after compactAll"); + + // For the inbox, which was compacted but not rebuild, key is unchanged. + let postInboxMsg3Key = + localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID( + gMsg3ID + ).messageKey; + Assert.equal(preInboxMsg3Key, postInboxMsg3Key); + + // For folder2, which was rebuilt, keys change but all messages should exist. + let message2 = gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID); + Assert.ok(message2); + Assert.ok(gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg3ID)); + + // In folder2, gMsg2ID is the first message. After compact with database + // rebuild, that key has now changed. + Assert.notEqual(message2.messageKey, f2m2Key); + }, + function lastTestCheck() { + Assert.equal( + gExpectedInboxSize, + localAccountUtils.inboxFolder.filePath.fileSize + ); + Assert.equal(gExpectedFolder2Size, gLocalFolder2.filePath.fileSize); + Assert.equal(gExpectedFolder3Size, gLocalFolder3.filePath.fileSize); + verifyMsgOffsets(gLocalFolder2); + verifyMsgOffsets(gLocalFolder3); + verifyMsgOffsets(localAccountUtils.inboxFolder); + }, +]; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + // Load up some messages so that we can copy them in later. + gMsgFile1 = do_get_file("../../../data/bugmail10"); + gMsgFile2 = do_get_file("../../../data/bugmail11"); + gMsgFile3 = do_get_file("../../../data/draft1"); + + // Create another folder to move and copy messages around, and force initialization. + gLocalFolder2 = localAccountUtils.rootFolder.createLocalSubfolder("folder2"); + + // Create a third folder for more testing. + gLocalFolder3 = localAccountUtils.rootFolder.createLocalSubfolder("folder3"); + + gTestArray.forEach(x => add_task(x)); + run_next_test(); +} + +// debug utility to show the key/offset/ID relationship of messages in a folder +function showMessages(folder, text) { + dump(`***** Show messages for folder <${folder.name}> "${text} *****\n`); + for (let hdr of folder.messages) { + dump( + ` key: ${hdr.messageKey} offset: ${hdr.messageOffset} size: ${hdr.messageSize} ID: ${hdr.messageId}\n` + ); + } +} diff --git a/comm/mailnews/base/test/unit/test_folderCompact2.js b/comm/mailnews/base/test/unit/test_folderCompact2.js new file mode 100644 index 0000000000..dc9d0b865a --- /dev/null +++ b/comm/mailnews/base/test/unit/test_folderCompact2.js @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +/* + * A few more specific mbox compaction tests for local folders: + * - mbox file contains exactly what we expect after compaction? + * test_folderCompact.js tends to rely on checking values in the msgDB. + * - works with messages larger than compaction code internal buffer? + * - X-Mozilla-Status/Status2/Keys headers handled as expected? + * + * Note: all these tests perform a seemingly-arbitrary delete. + * This is to trigger the folder compactor to actually do work. Without that + * delete it tends to think that compaction isn't required and does nothing. + */ + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +// Escape a string for more useful debug output - show EOLs and spaces. +function esc(s) { + s = s.replace(/\r/g, "\\r"); + s = s.replace(/\n/g, "\\n\n"); + s = s.replace(/ /g, "\u2420"); // U+2420 SYMBOL FOR SPACE + return s; +} + +// Load raw messages into folder (and sanitycheck them). +function loadMsgs(folder, inputMsgs) { + folder.addMessageBatch(inputMsgs); + // Make sure all the loaded messages are the expected size. + // If this fails, it probably means addMessageBatch() no longer assumes input + // data is mbox format, which is good! See Bug 1763263. + let hdrs = Array.from(folder.messages); + for (let i = 0; i < hdrs.length; ++i) { + Assert.equal( + inputMsgs[i].length, + hdrs[i].messageSize, + `Loaded message ${i} should match size in msgDB` + ); + } +} + +// Delete the specified messages. +async function deleteMsgs(folder, indexesToDelete) { + let hdrs = Array.from(folder.messages); + let doomed = indexesToDelete.map(i => hdrs[i]); + let listener = new PromiseTestUtils.PromiseCopyListener(); + folder.deleteMessages(doomed, null, false, true, listener, true); + await listener.promise; +} + +// Check the raw mbox file against what we expect to see. +async function checkMbox(folder, outputMsgs) { + let bytes = await IOUtils.read(folder.filePath.path); + let mbox = ""; + for (let b of bytes) { + mbox += String.fromCharCode(b); + } + + let expected = ""; + for (let raw of outputMsgs) { + // mbox has blank line between messages + expected += raw + "\n"; + } + + // Force all EOLs to linefeeds for comparisons. + // EOLs are handled inconsistently - most code will just leave EOLs as they + // come in, but new EOLs (e.g. added between messages in mbox) will use + // platform native EOLs. So our cheap and cheerful hack here is to just + // ditch all CRs and use pure LFs. + mbox = mbox.replace(/\r/g, ""); + expected = expected.replace(/\r/g, ""); + + if (mbox != expected) { + // Pretty-print before we assert. Makes life so much easier. + dump(`=======mbox=========\n${esc(mbox)}\n============\n`); + dump(`=======expected=====\n${esc(expected)}\n============\n`); + } + Assert.ok(mbox == expected, "mbox should contain expected data"); +} + +// Some chunks from which we'll construct test messages. + +// These are the default X-Mozilla- headers for local folders (they are +// re-written in place when flags and keywords are modified). +let xhdrs = + `X-Mozilla-Status: 0000\r\n` + + `X-Mozilla-Status2: 00010000\r\n` + // 'New' flag is set + `X-Mozilla-Keys: \r\n`; + +let hdrs1 = + "Date: Fri, 21 Nov 1997 09:26:06 -0600\r\n" + + "From: bob@invalid\r\n" + + "Subject: Test message 1\r\n" + + "Message-ID: <blah1@invalid>\r\n"; + +let bod1 = "Body of message 1.\r\n"; + +let hdrs2 = + "Date: Fri, 21 Nov 1997 10:55:32 -0600\r\n" + + "From: bob@invalid\r\n" + + "Subject: Test message 2\r\n" + + "Message-ID: <blah2@invalid>\r\n"; + +let bod2 = "Body of message2.\r\n"; + +let hdrs3 = + `Date: Fri, 21 Nov 1997 11:09:14 -0600\r\n` + + `From: bob@invalid\r\n` + + `Message-ID: <blah3@invalid>\r\n` + + `Subject: Test message 3\r\n`; + +let bod3 = `message\r\nthree\r\nis multiple\r\nlines.\r\n`; + +let from = "From \r\n"; + +// Check compact works after a simple delete. +add_task(async function testSimple() { + localAccountUtils.clearAll(); + localAccountUtils.loadLocalMailAccount(); + let inbox = localAccountUtils.inboxFolder; + + let inMsgs = [ + `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs2}\r\n${bod2}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + let doomed = [1]; // Delete message msg2. + // Out expected output: + let outMsgs = [ + `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + + loadMsgs(inbox, inMsgs); + await deleteMsgs(inbox, doomed); + + let l = new PromiseTestUtils.PromiseUrlListener(); + inbox.compact(l, null); + await l.promise; + + await checkMbox(inbox, outMsgs); +}); + +// Check that local folder compact adds missing X-Mozilla- headers. +add_task(async function testMissingXMozillaHdrs() { + localAccountUtils.clearAll(); + localAccountUtils.loadLocalMailAccount(); + let inbox = localAccountUtils.inboxFolder; + + // No X-Mozilla-* headers on input. + let inMsgs = [ + `${from}${hdrs1}\r\n${bod1}\r\n`, + `${from}${hdrs2}\r\n${bod2}\r\n`, + `${from}${hdrs3}\r\n${bod3}\r\n`, + ]; + let doomed = [1]; // Delete msg2. + // Out expected output. + // Compact should have added X-Mozilla-* headers. + let outMsgs = [ + `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + + loadMsgs(inbox, inMsgs); + await deleteMsgs(inbox, doomed); + + let l = new PromiseTestUtils.PromiseUrlListener(); + inbox.compact(l, null); + await l.promise; + + await checkMbox(inbox, outMsgs); +}); + +// Check localfolder compact works on messages are bigger than internal read buffer. +add_task(async function testBigMessages() { + localAccountUtils.clearAll(); + localAccountUtils.loadLocalMailAccount(); + let inbox = localAccountUtils.inboxFolder; + + // Compaction uses buffer of around 16KB, so we'll go way bigger. + let targSize = 256 * 1024; + + let inMsgs = []; + let outMsgs = []; + let doomed = [0, 1]; // We'll delete the first 2 messages. + for (let i = 0; i < 5; ++i) { + let raw = + `From \r\n` + + xhdrs + + `Date: Fri, 21 Nov 1997 09:55:06 -0600\r\n` + + `From: bob${i}@invalid\r\n` + + `Message-ID: <blah${i}@invalid>\r\n` + + `\r\n`; + while (raw.length < targSize) { + raw += + "BlahBlahBlahBlahBlahBlahBlahBlahBlah" + + "BlahBlahBlahBlahBlahBlahBlahBlahBlah\r\n"; + } + raw += `\r\n`; + inMsgs.push(raw); + if (!doomed.includes(i)) { + outMsgs.push(raw); + } + } + + loadMsgs(inbox, inMsgs); + + await deleteMsgs(inbox, doomed); + + let l = new PromiseTestUtils.PromiseUrlListener(); + inbox.compact(l, null); + await l.promise; + + // outMsgs is what we expect to see in the mbox. + await checkMbox(inbox, outMsgs); +}); + +// Check that local folder compact moves X-Mozilla-* headers to start of +// header block. +add_task(async function testMoveXMozillaHdrs() { + localAccountUtils.clearAll(); + localAccountUtils.loadLocalMailAccount(); + let inbox = localAccountUtils.inboxFolder; + + // These have X-Mozilla-* headers after all the other headers. + let inMsgs = [ + `${from}${hdrs1}${xhdrs}\r\n${bod1}\r\n`, + `${from}${hdrs2}${xhdrs}\r\n${bod2}\r\n`, + `${from}${hdrs3}${xhdrs}\r\n${bod3}\r\n`, + ]; + let doomed = [1]; // Delete msg2. + // The messages we expect to see in the final mbox. + // Compact should have moved the X-Mozilla-* headers to the front. + let outMsgs = [ + `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + + loadMsgs(inbox, inMsgs); + await deleteMsgs(inbox, doomed); + + let l = new PromiseTestUtils.PromiseUrlListener(); + inbox.compact(l, null); + await l.promise; + + await checkMbox(inbox, outMsgs); +}); + +// Check that local folder compact handles large X-Mozilla-Keys value. +add_task(async function testBigXMozillaKeys() { + localAccountUtils.clearAll(); + localAccountUtils.loadLocalMailAccount(); + let inbox = localAccountUtils.inboxFolder; + + let bigKeyword = + "HugeGreatBigStupidlyLongKeywordNameWhichWillDefinitelyOverflowThe80" + + "CharactersUsuallyReservedInTheKeywordsHeaderForInPlaceEditing"; + + let inMsgs = [ + `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs2}\r\n${bod2}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + let doomed = [1]; // Delete msg2. + + let bigxhdrs = + `X-Mozilla-Status: 0000\r\n` + + `X-Mozilla-Status2: 00010000\r\n` + // 'New' flag is set + `X-Mozilla-Keys: ${bigKeyword}\r\n`; + + // The messages we expect to see in the final mbox: + let outMsgs = [ + `${from}${bigxhdrs}${hdrs1}\r\n${bod1}\r\n`, + `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`, + ]; + + loadMsgs(inbox, inMsgs); + + let msgs = Array.from(inbox.messages); + inbox.addKeywordsToMessages([msgs[0]], bigKeyword); + + await deleteMsgs(inbox, doomed); + + let l = new PromiseTestUtils.PromiseUrlListener(); + inbox.compact(l, null); + await l.promise; + + await checkMbox(inbox, outMsgs); +}); diff --git a/comm/mailnews/base/test/unit/test_folderLookupService.js b/comm/mailnews/base/test/unit/test_folderLookupService.js new file mode 100644 index 0000000000..039fa2a383 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_folderLookupService.js @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This tests that nsIFolderLookupService works according to specification. + */ +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +var kRootURI = "mailbox://nobody@Local%20Folders"; + +add_task(async function test_fls_basics() { + let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService( + Ci.nsIFolderLookupService + ); + + // Make sure that the local mail account exists. + localAccountUtils.loadLocalMailAccount(); + + // There should exist an inbox. + Assert.equal( + fls.getFolderForURL(kRootURI + "/Inbox"), + localAccountUtils.inboxFolder + ); + + // Can we get the root folder? + let root = localAccountUtils.rootFolder; + Assert.equal(fls.getFolderForURL(kRootURI), root); + + // The child folder Child doesn't exist yet... make sure that fls doesn't + // return it yet + Assert.equal(fls.getFolderForURL(kRootURI + "/Child"), null); + + // Create the Child folder, and test we can find it. + // (NOTE: createSubFolder() can be async for IMAP folders. But for now + // we know we're using local folders and it'll happen right away). + root.createSubfolder("Child", null); + Assert.equal( + fls.getFolderForURL(kRootURI + "/Child"), + root.getChildNamed("Child") + ); + + // Try it again... it should load from cache this time. + Assert.equal( + fls.getFolderForURL(kRootURI + "/Child"), + root.getChildNamed("Child") + ); + + // Now delete the folder; we should be unable to find it + root.propagateDelete(root.getChildNamed("Child"), true); + Assert.equal(fls.getFolderForURL(kRootURI + "/Child"), null); + + Assert.equal(fls.getFolderForURL(kRootURI + "/"), null); + Assert.equal( + fls.getFolderForURL("mailbox://idonotexist@Local%20Folders"), + null + ); +}); + +add_task(async function test_unicode_uris() { + let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService( + Ci.nsIFolderLookupService + ); + localAccountUtils.loadLocalMailAccount(); + let root = localAccountUtils.rootFolder; + + // Create a folder with non-ASCII characters. + // Unicode abuse - dotless letter i and a metal umlaut over the n. + let tapName = "Sp\u0131n\u0308al Tap"; + root.createSubfolder(tapName, null); + + // Make sure we can find it. + // (URI is percent-escaped utf-8) + let tapNameEscaped = "Sp%C4%B1n%CC%88al%20Tap"; + if (AppConstants.platform == "win") { + // For !ConvertibleToNative(), folder name is hashed on Windows. + tapNameEscaped = "a2d874f7"; + } + let tapURI = kRootURI + "/" + tapNameEscaped; + let tap = fls.getFolderForURL(tapURI); + Assert.equal(tap.URI, tapURI); +}); diff --git a/comm/mailnews/base/test/unit/test_folderStringProperties.js b/comm/mailnews/base/test/unit/test_folderStringProperties.js new file mode 100644 index 0000000000..8f508625b7 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_folderStringProperties.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test the nsIMsgFolder .(get|set)StringProperty methods. + */ + +add_task(function test_string_properties() { + localAccountUtils.loadLocalMailAccount(); + let root = localAccountUtils.incomingServer.rootMsgFolder; + + // Ensure unset properties return an error. + Assert.throws(function () { + root.getStringProperty("this-property-doesnt-exist"); + }, /NS_ERROR_.*/); + + // Check basic set/get operation. + root.setStringProperty("test-property", "wibble"); + Assert.equal(root.getStringProperty("test-property"), "wibble"); + + // Keys are case-sensitive. + Assert.throws(function () { + root.getStringProperty("TEST-PROPERTY"); + }, /NS_ERROR_.*/); + + // Values with non-latin chars? + root.setStringProperty("test-property", "日本語"); + Assert.equal(root.getStringProperty("test-property"), "日本語"); + + // Check that things stay as strings, even if they are values that could + // be misinterpreted in JSON. + root.setStringProperty("test-property", ""); + Assert.equal(root.getStringProperty("test-property"), ""); + + root.setStringProperty("test-property", "null"); + Assert.equal(root.getStringProperty("test-property"), "null"); + + root.setStringProperty("test-property", "0"); + Assert.equal(root.getStringProperty("test-property"), "0"); +}); diff --git a/comm/mailnews/base/test/unit/test_formatFileSize.js b/comm/mailnews/base/test/unit/test_formatFileSize.js new file mode 100644 index 0000000000..1269fd33c3 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_formatFileSize.js @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests for the formatFileSize method. + */ + +var gStringBundle = Services.strings.createBundle( + "chrome://messenger/locale/messenger.properties" +); + +var gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger); + +function isDigit(c) { + return "0123456789".includes(c); +} + +function test_formatFileSize(aArgs) { + const strings = { + b: "byteAbbreviation2", + kb: "kiloByteAbbreviation2", + mb: "megaByteAbbreviation2", + gb: "gigaByteAbbreviation2", + tb: "teraByteAbbreviation2", + pb: "petaByteAbbreviation2", + }; + + let actual = gMessenger.formatFileSize(aArgs.bytes, aArgs.useKB); + let expected = gStringBundle + .GetStringFromName(strings[aArgs.units]) + .replace("%.*f", aArgs.mantissa); + + // If the actual string contains a non-numeric character at the position + // where we'd expect a decimal separator, assume it is a localized separator + // and just convert it to a dot for easy comparing. + let separatorPos = aArgs.mantissa.indexOf("."); + if (!isDigit(actual.charAt(separatorPos))) { + actual = + actual.substring(0, separatorPos) + "." + actual.substr(separatorPos + 1); + } + + Assert.equal(actual, expected); +} + +var test_data = [ + { bytes: 0, useKB: false, mantissa: "0", units: "b" }, + { bytes: 1, useKB: false, mantissa: "1", units: "b" }, + { bytes: 10, useKB: false, mantissa: "10", units: "b" }, + { bytes: 999, useKB: false, mantissa: "999", units: "b" }, + { bytes: 1000, useKB: false, mantissa: "1.0", units: "kb" }, + { bytes: 1024, useKB: false, mantissa: "1.0", units: "kb" }, + { bytes: 10 * 1024, useKB: false, mantissa: "10.0", units: "kb" }, + { bytes: 999 * 1024, useKB: false, mantissa: "999", units: "kb" }, + { bytes: 1000 * 1024, useKB: false, mantissa: "1.0", units: "mb" }, + { bytes: 1024 * 1024, useKB: false, mantissa: "1.0", units: "mb" }, + { bytes: 10 * 1024 * 1024, useKB: false, mantissa: "10.0", units: "mb" }, + { bytes: 999 * 1024 * 1024, useKB: false, mantissa: "999", units: "mb" }, + { bytes: 1000 * 1024 * 1024, useKB: false, mantissa: "1.0", units: "gb" }, + { bytes: 1024 * 1024 * 1024, useKB: false, mantissa: "1.0", units: "gb" }, + { + bytes: 10 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "10.0", + units: "gb", + }, + { + bytes: 999 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "999", + units: "gb", + }, + { + bytes: 1000 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1.0", + units: "tb", + }, + { + bytes: 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1.0", + units: "tb", + }, + { + bytes: 10 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "10.0", + units: "tb", + }, + { + bytes: 999 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "999", + units: "tb", + }, + { + bytes: 1000 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1.0", + units: "pb", + }, + { + bytes: 1000 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1.0", + units: "pb", + }, + { + bytes: 1024 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1.0", + units: "pb", + }, + { + bytes: 10 * 1024 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "10.0", + units: "pb", + }, + { + bytes: 999 * 1024 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "999", + units: "pb", + }, + { + bytes: 1000 * 1024 * 1024 * 1024 * 1024 * 1024, + useKB: false, + mantissa: "1000", + units: "pb", + }, + + { bytes: 0, useKB: true, mantissa: "0", units: "kb" }, + { bytes: 1, useKB: true, mantissa: "0.1", units: "kb" }, + { bytes: 500, useKB: true, mantissa: "0.5", units: "kb" }, + { bytes: 999, useKB: true, mantissa: "1.0", units: "kb" }, +]; + +add_task(function test_format_file_size() { + test_data.map(entry => { + test_formatFileSize(entry); + }); +}); diff --git a/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js b/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js new file mode 100644 index 0000000000..b803619e21 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test suite for GetMsgTextFromStream. + * + * Currently tests: text/plain, text/html -- with tags stripped and without, + * base64, multipart. + * Does not currently test: quoted-printable, stripping quotes, UTF-8, small values of + * bytesToRead. + */ +var kDataRoot = "../../../data/"; + +function create(fileName, bytes, compressQuotes, stripHTML, outContentType) { + return { + name: fileName, + bytesToRead: bytes, + compressQuotes, + stripHTML, + contentType: outContentType, + }; +} + +var gTestFiles = [ + create("basic1", 1024, false, false, "text/plain"), // Simple plain text + create("basic1", 1024, false, true, "text/plain"), // should be same as above + create("basic2", 1024, false, false, "text/html"), // Simple HTML + create("basic3", 1024, false, true, "text/html"), // HTML with tags stripped out + create("basic4", 1024, false, false, "text/plain"), // No content type, should be assumed to be text/plain + create("basic4", 1024, false, true, "text/plain"), + create("basic5", 1024, false, false, "text/plain"), // HTML content in text/plain + create("basic5", 1024, false, true, "text/plain"), + create("base64-1", 1024, false, false, "text/plain"), // base64 text/plain + create("base64-1", 1024, false, true, "text/plain"), + create("base64-2", 1024, false, false, "text/html"), // base64 text/html + create("base64-3", 1024, false, true, "text/html"), // strip out tags here + create("multipart1", 1024, false, false, "text/plain"), // basic multipart message + create("multipart1", 1024, false, true, "text/plain"), + create("multipart2", 1024, false, false, "text/html"), // multipart HTML + create("multipart3", 1024, false, true, "text/html"), + create("multipart4", 1024, false, false, "text/plain"), // text with no headers + create("multipart4", 1024, false, true, "text/plain"), + create("multipart-base64-1", 1024, false, false, "text/plain"), // base64 encoded text + create("multipart-base64-1", 1024, false, true, "text/plain"), + create("multipart-base64-2", 1024, false, false, "text/html"), + create("multipart-base64-3", 1024, false, true, "text/html"), + create("multipart-complex1", 1024, false, true, "text/html"), // Things get more complex here + create("multipart-complex2", 1024, false, false, "text/plain"), + create("multipart-complex2", 1024, false, true, "text/plain"), +]; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + var folder = localAccountUtils.incomingServer.rootMsgFolder; + + gTestFiles.forEach(function (test) { + dump("Testing " + test.name + "\n"); + var inFile = do_get_file(kDataRoot + test.name); + var inStream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + inStream.init(inFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); + + // Now get the message body using getMsgTextFromStream + var contentType = {}; + var body = folder.getMsgTextFromStream( + inStream, + "", + test.bytesToRead, + 65536, + test.compressQuotes, + test.stripHTML, + contentType + ); + + // Now we need to compare the output + Assert.equal(test.contentType, contentType.value); + + var resultFile = do_get_file(kDataRoot + test.name + ".out"); + var actualBody = mailTestUtils.loadFileToString(resultFile, "UTF-8"); + Assert.equal(body, actualBody); + }); +} diff --git a/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js b/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js new file mode 100644 index 0000000000..c3165ba720 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Testing header folding in nsParseMailMessageState::ParseHeaders(), + * see bug 1454257 and bug 1456001. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var hdr; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + var copyListener = { + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey); + }, + SetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + continueTest(); + }, + }; + + // Get a message into the local filestore. + var message = do_get_file("../../../data/badly-folded-headers.eml"); + do_test_pending(); + MailServices.copy.copyFileMessage( + message, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + null + ); +} + +function continueTest() { + Assert.equal(hdr.author, "sender@example.com"); + Assert.equal( + hdr.recipients, + '"Recipient with spaces" <recipient@example.com>' + ); + Assert.equal( + hdr.subject, + "Badly folded headers, one line with space between To and From" + ); + hdr = null; + do_test_finished(); +} diff --git a/comm/mailnews/base/test/unit/test_hostnameUtils.js b/comm/mailnews/base/test/unit/test_hostnameUtils.js new file mode 100644 index 0000000000..5f8839165d --- /dev/null +++ b/comm/mailnews/base/test/unit/test_hostnameUtils.js @@ -0,0 +1,276 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests for hostnameUtils.jsm. + */ + +var { + isLegalHostName, + isLegalHostNameOrIP, + isLegalIPAddress, + isLegalIPv4Address, + isLegalIPv6Address, + isLegalLocalIPAddress, +} = ChromeUtils.import("resource:///modules/hostnameUtils.jsm"); + +/** + * Checks if valid and invalid IPs are properly allowed or rejected. + */ +function test_IPaddresses() { + const kIPsToTest = [ + // isValid, IP addr. isIPv6, isLocal, extend, result + // IPv4 + [true, "1.2.3.4", false, false, false], + [true, "123.245.111.222", false, false, false], + [true, "255.255.255.255", false, false, false], + [true, "1.2.0.4", false, false, false], + [true, "1.2.3.4", false, false, false], + [true, "127.1.2.3", false, true, false], + [true, "10.1.2.3", false, true, false], + [true, "192.168.2.3", false, true, false], + + [false, "1.2.3.4.5", false, false, false], + [false, "1.2.3", false, false, false], + [false, "1.2.3.", false, false, false], + [false, ".1.2.3", false, false, false], + [false, "1.2.3.256", false, false, false], + [false, "1.2.3.12345", false, false, false], + [false, "1.2..123", false, false, false], + [false, "1", false, false, false], + [false, "", false, false, false], + [false, "0.1.2.3", false, false, false], + [false, "0.0.2.3", false, false, false], + [false, "0.0.0.0", false, false, false], + [false, "1.2.3.d", false, false, false], + [false, "a.b.c.d", false, false, false], + [false, "a.b.c.d", false, false, true], + // IPv6 + [ + true, + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + true, + false, + false, + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + ], + [ + true, + "2001:db8:85a3:0:0:8a2e:370:7334", + true, + false, + false, + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + ], + [ + true, + "2001:db8:85a3::8a2e:370:7334", + true, + false, + false, + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + ], + [ + true, + "2001:0db8:85a3:0000:0000:8a2e:0370:", + true, + false, + false, + "2001:0db8:85a3:0000:0000:8a2e:0370:0000", + ], + [ + true, + "::ffff:c000:0280", + true, + false, + false, + "0000:0000:0000:0000:0000:ffff:c000:0280", + ], + [ + true, + "::ffff:192.0.2.128", + true, + false, + false, + "0000:0000:0000:0000:0000:ffff:c000:0280", + ], + [ + true, + "2001:db8::1", + true, + false, + false, + "2001:0db8:0000:0000:0000:0000:0000:0001", + ], + [ + true, + "2001:DB8::1", + true, + false, + false, + "2001:0db8:0000:0000:0000:0000:0000:0001", + ], + [ + true, + "1:2:3:4:5:6:7:8", + true, + false, + false, + "0001:0002:0003:0004:0005:0006:0007:0008", + ], + + [true, "::1", true, true, false, "0000:0000:0000:0000:0000:0000:0000:0001"], + [ + true, + "::0000:0000:1", + true, + true, + false, + "0000:0000:0000:0000:0000:0000:0000:0001", + ], + + [false, "::", true, false, false], + [false, "2001:0db8:85a3:0000:0000:8a2e:0370:73346", true, false, false], + [false, "2001:0db8:85a3:0000:0000:8a2e:0370:7334:1", true, false, false], + [false, "2001:0db8:85a3:0000:0000:8a2e:0370:7334x", true, false, false], + [false, "2001:0db8:85a3:0000:0000:8a2e:03707334", true, false, false], + [false, "2001:0db8:85a3:0000:0000x8a2e:0370:7334", true, false, false], + [false, "2001:0db8:85a3:0000:0000:::1", true, false, false], + [false, "2001:0db8:85a3:0000:0000:0000:some:junk", true, false, false], + [false, "2001:0db8:85a3:0000:0000:0000::192.0.2.359", true, false, false], + [false, "some::junk", true, false, false], + [false, "some_junk", true, false, false], + + // Extended formats of IPv4, hex, octal, decimal up to DWORD + [true, "0xff.0x12.0x45.0x78", false, false, true, "255.18.69.120"], + [true, "01.0123.056.077", false, false, true, "1.83.46.63"], + [true, "0xff.2.3.4", false, false, true, "255.2.3.4"], + [true, "0xff.2.3.077", false, false, true, "255.2.3.63"], + [true, "0x7f.2.3.077", false, true, true, "127.2.3.63"], + + [false, "0xZZ.1.2.3", false, false, true], + [false, "0x00.0123.056.077", false, false, true], + [false, "0x11.0123.056.078", false, false, true], + [false, "0x11.0123.056.0789", false, false, true], + + [true, "1234566945", false, false, true, "73.149.255.33"], + [false, "12345", false, false, true], + [false, "123456789123456", false, false, true], + + [true, "127.1", false, true, true, "127.0.0.1"], + [true, "0x7f.100", false, true, true, "127.0.0.100"], + [true, "0x7f.100.1000", false, true, true, "127.100.3.232"], + [true, "0xff.100.1024", false, false, true, "255.100.4.0"], + [true, "0xC0.0xA8.0x2A48", false, true, true, "192.168.42.72"], + [true, "0xC0.0xA82A48", false, true, true, "192.168.42.72"], + [true, "0xC0A82A48", false, true, true, "192.168.42.72"], + [true, "0324.062477106", false, false, true, "212.202.126.70"], + + [false, "0.0.1000", false, false, true], + [false, "0324.06247710677", false, false, true], + ]; + + for (let item of kIPsToTest) { + let result = null; + let [isValid, address, isIPv6, isLocal, isExtended, wantedResult] = item; + if (!wantedResult) { + wantedResult = isValid ? address : null; + } + + if (isIPv6) { + result = isLegalIPv6Address(address); + Assert.equal(result, wantedResult); + if (isValid) { + // If this is valid IPv6, it can't be valid IPv4. The opposite is unknown. + result = isLegalIPv4Address(address); + Assert.equal(result, null); + } + } else { + result = isLegalIPv4Address(address, isExtended); + Assert.equal(result, wantedResult); + if (isValid) { + // If this is valid IPv4, it can't be valid IPv6. The opposite is unknown. + result = isLegalIPv6Address(address); + Assert.equal(result, null); + } + } + + result = isLegalIPAddress(address, isExtended); + Assert.equal(result, wantedResult); + + if (isValid) { + // isLegalLocalIPAddress operates on a normalized address, + // not the original one. + result = isLegalLocalIPAddress(result); + Assert.equal(result, isLocal); + } + + // If something is a valid IP, it also passes isLegalHostNameOrIP. + // However, an invalid IP string may still be a valid hostname. + // So only check success if the IP is valid. + result = isLegalHostNameOrIP(address, isExtended); + if (isValid) { + Assert.equal(result, wantedResult); + } + } +} +/** + * Checks if valid and invalid host names are properly allowed or rejected. + */ +function test_hostnames() { + const kHostsToTest = [ + // isValid, hostname + [true, "localhost"], + [true, "some-server"], + [true, "server.company.invalid"], + [true, "server.comp-any.invalid"], + [true, "server.123.invalid"], + [true, "1server.123.invalid"], + [true, "1.2.3.4.5"], + [true, "very.log.sub.domain.name.invalid"], + [true, "1234567890"], + [true, "1234567890."], // FQDN + [true, "server.company.invalid."], // FQDN + + [false, ""], + [false, "server.badcompany!.invalid"], + [false, "server._badcompany.invalid"], + [false, "server.bad_company.invalid"], + [false, "server.badcompany-.invalid"], + [false, "server.bad company.invalid"], + [false, "server.b…dcompany.invalid"], + [false, ".server.badcompany.invalid"], + [ + false, + "make-this-a-long-host-name-component-that-is-over-63-characters-long.invalid", + ], + [ + false, + "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." + + "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." + + "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." + + "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid", + ], + ]; + + for (let item of kHostsToTest) { + let result = null; + let [wantedResult, hostname] = item; + wantedResult = wantedResult ? hostname : null; + + result = isLegalHostName(hostname); + Assert.equal(result, wantedResult); + + result = isLegalHostNameOrIP(hostname, false); + Assert.equal(result, wantedResult); + } +} + +var gTests = [test_IPaddresses, test_hostnames]; + +function run_test() { + for (let test of gTests) { + test(); + } +} diff --git a/comm/mailnews/base/test/unit/test_identity.js b/comm/mailnews/base/test/unit/test_identity.js new file mode 100644 index 0000000000..c28615451a --- /dev/null +++ b/comm/mailnews/base/test/unit/test_identity.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests the UID attribute of identities. + */ +add_task(async function testUID() { + const UUID_REGEXP = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + + // Create an identity and check it the UID is set when accessed. + + let identityA = MailServices.accounts.createIdentity(); + Assert.stringMatches( + identityA.UID, + UUID_REGEXP, + "identity A's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.identity.${identityA.key}.uid`), + identityA.UID, + "identity A's UID should be saved to the preferences" + ); + Assert.throws( + () => (identityA.UID = "00001111-2222-3333-4444-555566667777"), + /NS_ERROR_ABORT/, + "identity A's UID should be unchangeable after it is set" + ); + + // Create a second identity and check the two UIDs don't match. + + let identityB = MailServices.accounts.createIdentity(); + Assert.stringMatches( + identityB.UID, + UUID_REGEXP, + "identity B's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.identity.${identityB.key}.uid`), + identityB.UID, + "identity B's UID should be saved to the preferences" + ); + Assert.notEqual( + identityB.UID, + identityA.UID, + "identity B's UID should not be the same as identity A's" + ); + + // Create a third identity and set the UID before it is accessed. + + let identityC = MailServices.accounts.createIdentity(); + identityC.UID = "11112222-3333-4444-5555-666677778888"; + Assert.equal( + identityC.UID, + "11112222-3333-4444-5555-666677778888", + "identity C's UID set correctly" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.identity.${identityC.key}.uid`), + "11112222-3333-4444-5555-666677778888", + "identity C's UID should be saved to the preferences" + ); + Assert.throws( + () => (identityC.UID = "22223333-4444-5555-6666-777788889999"), + /NS_ERROR_ABORT/, + "identity C's UID should be unchangeable after it is set" + ); +}); diff --git a/comm/mailnews/base/test/unit/test_imapPump.js b/comm/mailnews/base/test/unit/test_imapPump.js new file mode 100644 index 0000000000..ccc34bae60 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_imapPump.js @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Simple demonstration of the imap pump test method. + */ + +// async support +/* import-globals-from ../../../test/resources/logHelper.js */ +load("../../../resources/logHelper.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +// IMAP pump +var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import( + "resource://testing-common/mailnews/IMAPpump.jsm" +); +var { ImapMessage } = ChromeUtils.import( + "resource://testing-common/mailnews/Imapd.jsm" +); + +var { fsDebugAll } = ChromeUtils.import( + "resource://testing-common/mailnews/Maild.jsm" +); + +// Globals + +// Messages to load must have CRLF line endings, that is Windows style +var gMessage = "bugmail10"; // message file used as the test message + +// Definition of tests + +// load and update a message in the imap fake server + +var gTestArray = [ + // initial setup of IMAP environment + setupIMAPPump, + + // optionally set server parameters, here enabling debug messages + function serverParms() { + IMAPPump.server.setDebugLevel(fsDebugAll); + }, + + // the main test + async function loadImapMessage() { + IMAPPump.mailbox.addMessage( + new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, []) + ); + let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener); + await promiseUrlListener.promise; + + Assert.equal(1, IMAPPump.inbox.getTotalMessages(false)); + let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox); + Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr); + }, + + // all done + teardownIMAPPump, +]; + +add_setup(() => { + Services.prefs.setBoolPref( + "mail.server.default.autosync_offline_stores", + false + ); + gTestArray.forEach(x => add_task(x)); +}); + +/* + * helper functions + */ + +// given a test file, return the file uri spec +function specForFileName(aFileName) { + let file = do_get_file(gDEPTH + "mailnews/data/" + aFileName); + let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL); + return msgfileuri.spec; +} diff --git a/comm/mailnews/base/test/unit/test_incomingServer.js b/comm/mailnews/base/test/unit/test_incomingServer.js new file mode 100644 index 0000000000..9093ab4806 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_incomingServer.js @@ -0,0 +1,99 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +function subtestUID(type) { + const UUID_REGEXP = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + + // Create a server and check it the UID is set when accessed. + + let serverA = MailServices.accounts.createIncomingServer( + "userA", + "hostA", + type + ); + Assert.stringMatches( + serverA.UID, + UUID_REGEXP, + "server A's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.server.${serverA.key}.uid`), + serverA.UID, + "server A's UID should be saved to the preferences" + ); + Assert.throws( + () => (serverA.UID = "00001111-2222-3333-4444-555566667777"), + /NS_ERROR_ABORT/, + "server A's UID should be unchangeable after it is set" + ); + + // Create a second server and check the two UIDs don't match. + + let serverB = MailServices.accounts.createIncomingServer( + "userB", + "hostB", + type + ); + Assert.stringMatches( + serverB.UID, + UUID_REGEXP, + "server B's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.server.${serverB.key}.uid`), + serverB.UID, + "server B's UID should be saved to the preferences" + ); + Assert.notEqual( + serverB.UID, + serverA.UID, + "server B's UID should not be the same as server A's" + ); + + // Create a third server and set the UID before it is accessed. + + let serverC = MailServices.accounts.createIncomingServer( + "userC", + "hostC", + type + ); + serverC.UID = "11112222-3333-4444-5555-666677778888"; + Assert.equal( + serverC.UID, + "11112222-3333-4444-5555-666677778888", + "server C's UID set correctly" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.server.${serverC.key}.uid`), + "11112222-3333-4444-5555-666677778888", + "server C's UID should be saved to the preferences" + ); + Assert.throws( + () => (serverC.UID = "22223333-4444-5555-6666-777788889999"), + /NS_ERROR_ABORT/, + "server C's UID should be unchangeable after it is set" + ); +} + +/** + * Tests the UID attribute of IMAP servers. + */ +add_task(function testUID_IMAP() { + subtestUID("imap"); +}); + +/** + * Tests the UID attribute of NNTP servers. + */ +add_task(function testUID_NNTP() { + subtestUID("nntp"); +}); + +/** + * Tests the UID attribute of POP3 servers. + */ +add_task(function testUID_POP3() { + subtestUID("pop3"); +}); diff --git a/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js b/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js new file mode 100644 index 0000000000..55fb1da6c8 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Testing of inherited folder properties + */ + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + var rootFolder = localAccountUtils.incomingServer.rootMsgFolder; + + // add subfolders to the inbox + const subFolder11 = localAccountUtils.inboxFolder + .createLocalSubfolder("subfolder11") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + const subFolder12 = localAccountUtils.inboxFolder + .createLocalSubfolder("subfolder12") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + const subFolder21 = subFolder11.createLocalSubfolder("subfolder21"); + const subFolder22 = subFolder12.createLocalSubfolder("subfolder22"); + + // add a global preference + const propertyName = "iexist"; + const invalidName = "idontexist"; + const globalPref = "mail.server.default." + propertyName; + const globalValue = "iAmGlobal"; + const folderValue = "iAmFolder"; + const folderValue2 = "iAmFolder2"; + const rootValue = "iAmRoot"; + Services.prefs.setCharPref(globalPref, globalValue); + + // test that the global preference is honored + Assert.equal( + rootFolder.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder22.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal(rootFolder.getInheritedStringProperty(invalidName), null); + Assert.equal(subFolder11.getInheritedStringProperty(invalidName), null); + Assert.equal(subFolder22.getInheritedStringProperty(invalidName), null); + + // set a value on a subfolder and check + subFolder11.setStringProperty(propertyName, folderValue); + Assert.equal( + rootFolder.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal( + subFolder12.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal( + subFolder22.getInheritedStringProperty(propertyName), + globalValue + ); + + // set a root folder value and check + localAccountUtils.incomingServer.setCharValue(propertyName, rootValue); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue); + + // force an empty string + subFolder12.setForcePropertyEmpty(propertyName, true); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), ""); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), ""); + + // reset a folder to allow inheritance + subFolder12.setForcePropertyEmpty(propertyName, false); + subFolder12.setStringProperty(propertyName, ""); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue); + + // force an empty string on the server + localAccountUtils.incomingServer.setForcePropertyEmpty(propertyName, true); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), ""); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), ""); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), ""); + + // reset a server to allow inheritance from the global + localAccountUtils.incomingServer.setCharValue(propertyName, ""); + localAccountUtils.incomingServer.setForcePropertyEmpty(propertyName, false); + Assert.equal( + rootFolder.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal( + subFolder12.getInheritedStringProperty(propertyName), + globalValue + ); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal( + subFolder22.getInheritedStringProperty(propertyName), + globalValue + ); + + // check with all levels populated + subFolder21.setStringProperty(propertyName, folderValue2); + localAccountUtils.incomingServer.setCharValue(propertyName, rootValue); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue2 + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue); + + // clear the global value and the root value + Services.prefs.clearUserPref(globalPref); + localAccountUtils.incomingServer.setCharValue(propertyName, ""); + Assert.equal(rootFolder.getInheritedStringProperty(propertyName), null); + Assert.equal( + subFolder11.getInheritedStringProperty(propertyName), + folderValue + ); + Assert.equal(subFolder12.getInheritedStringProperty(propertyName), null); + Assert.equal( + subFolder21.getInheritedStringProperty(propertyName), + folderValue2 + ); + Assert.equal(subFolder22.getInheritedStringProperty(propertyName), null); +} diff --git a/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js b/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js new file mode 100644 index 0000000000..b6a103b069 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that junk actions work even when the bayes filtering of incoming + * messages is disabled, as fixed in bug 487610. Test developed by Kent + * James using test_nsMsgDBView.js as a base. + */ + +const { TreeSelection } = ChromeUtils.importESModule( + "chrome://messenger/content/tree-selection.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); + +var nsIMFNService = Ci.nsIMsgFolderNotificationService; + +// fake objects needed to get nsMsgDBView to operate on selected messages. +// Warning: these are partial implementations. If someone adds additional +// calls to these objects in nsMsgDBView and friends, it will also +// be necessary to add fake versions of those calls here. + +var gFakeSelection = new TreeSelection(null); + +// Items used to add messages to the folder + +var gMessageGenerator = new MessageGenerator(); + +var messageInjection = new MessageInjection( + { mode: "local" }, + gMessageGenerator +); + +var gLocalInboxFolder = messageInjection.getInboxFolder(); +var gListener; +var gCommandUpdater; + +var gDBView; +var gTreeView; + +var CommandUpdaterWithPromise = function () { + this.deferred = PromiseUtils.defer(); +}; +CommandUpdaterWithPromise.prototype = { + async promiseSelectionSummarized() { + await this.deferred.promise; + this.deferred = PromiseUtils.defer(); + return this.deferred.promise; + }, + + updateCommandStatus() { + // the back end is smart and is only telling us to update command status + // when the # of items in the selection has actually changed. + }, + + displayMessageChanged(aFolder, aSubject, aKeywords) {}, + + updateNextMessageAfterDelete() {}, + summarizeSelection() { + this.deferred.resolve(); + }, +}; + +// Our listener, which captures events and does the real tests. +function gMFListener() { + this._promiseMsgsMoveCopyCompleted = new Promise(resolve => { + this._resolveMsgsMoveCopyCompleted = resolve; + }); + this._promiseFolderAdded = new Promise(resolve => { + this._resolveFolderAdded = resolve; + }); +} +gMFListener.prototype = { + msgsMoveCopyCompleted(aMove, aSrcMsgs, aDestFolder, aDestMsgs) { + Assert.ok(aDestFolder.getFlag(Ci.nsMsgFolderFlags.Junk)); + // I tried to test this by counting messages in the folder, didn't work. + // Maybe all updates are not completed yet. Anyway I do it by just + // making sure there is something in the destination array. + Assert.ok(aDestMsgs.length > 0); + this._resolveMsgsMoveCopyCompleted(); + }, + + folderAdded(aFolder) { + // this should be a junk folder + Assert.ok(aFolder.getFlag(Ci.nsMsgFolderFlags.Junk)); + this._resolveFolderAdded(); + }, + get promiseMsgsMoveCopyCompleted() { + return this._promiseMsgsMoveCopyCompleted; + }, + get promiseFolderAdded() { + return this._promiseFolderAdded; + }, +}; + +add_setup(async function () { + // Set option so that when messages are marked as junk, they move to the junk folder + Services.prefs.setBoolPref("mail.spam.manualMark", true); + + // 0 == "move to junk folder", 1 == "delete" + Services.prefs.setIntPref("mail.spam.manualMarkMode", 0); + + // Disable bayes filtering on the local account. That's the whole point of this test, + // to make sure that the junk move happens anyway. + gLocalInboxFolder.server.spamSettings.level = 0; + + // Add folder listeners that will capture async events. + let flags = nsIMFNService.msgsMoveCopyCompleted | nsIMFNService.folderAdded; + gListener = new gMFListener(); + MailServices.mfn.addListener(gListener, flags); + + // Build up a message. + await messageInjection.makeNewSetsInFolders([gLocalInboxFolder], [{}]); + let view_type = "threaded"; + let view_flag = Ci.nsMsgViewFlagsType.kThreadedDisplay; + let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + view_type; + + // Always start out fully expanded. + view_flag |= Ci.nsMsgViewFlagsType.kExpandAll; + + gCommandUpdater = new CommandUpdaterWithPromise(); + + gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView); + gDBView.init(null, null, null); + var outCount = {}; + gDBView.open( + gLocalInboxFolder, + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.ascending, + view_flag, + outCount + ); + + gTreeView = gDBView.QueryInterface(Ci.nsITreeView); + gTreeView.selection = gFakeSelection; + gFakeSelection.view = gTreeView; +}); + +add_task(async function test_first_junking_create_folder() { + // In the proposed fix for bug 487610, the first call to junk messages + // only creates the junk folder, it does not actually successfully move + // messages. So we junk messages twice so we can really see a move. But + // if that gets fixed and the messages actually move on the first call, + // I want this test to succeed as well. So I don't actually count how + // many messages get moved, just that some do on the second move. + + // Select and junk all messages. + gDBView.doCommand(Ci.nsMsgViewCommandType.selectAll); + gDBView.doCommand(Ci.nsMsgViewCommandType.junk); + await gCommandUpdater.promiseSelectionSummarized; + await gListener.promiseFolderAdded; +}); + +add_task(async function test_add_further_message() { + // Add another message in case the first one moved. + await messageInjection.makeNewSetsInFolders([gLocalInboxFolder], [{}]); +}); + +add_task(async function test_second_junking_move_msgs() { + // Select and junk all messages. + gDBView.doCommand(Ci.nsMsgViewCommandType.selectAll); + gDBView.doCommand(Ci.nsMsgViewCommandType.junk); + await gCommandUpdater.promiseSelectionSummarized; + await gListener.promiseMsgsMoveCopyCompleted; +}); diff --git a/comm/mailnews/base/test/unit/test_loadVirtualFolders.js b/comm/mailnews/base/test/unit/test_loadVirtualFolders.js new file mode 100644 index 0000000000..13afd3f03b --- /dev/null +++ b/comm/mailnews/base/test/unit/test_loadVirtualFolders.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test loading of virtualFolders.dat, including verification of the search +// scopes, i.e., folder uri's. + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +// As currently written, this test will only work with Berkeley store. +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +// main test + +function run_test() { + let vfdat = do_get_file("../../../data/test_virtualFolders.dat"); + + vfdat.copyTo(do_get_profile(), "virtualFolders.dat"); + localAccountUtils.loadLocalMailAccount(); + let localMailDir = do_get_profile().clone(); + localMailDir.append("Mail"); + localMailDir.append("Local Folders"); + localMailDir.append("unread-local"); + localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + localMailDir.leafName = "invalidserver-local"; + localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + localMailDir.leafName = "$label1"; + localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + MailServices.accounts.loadVirtualFolders(); + let unreadLocal = + localAccountUtils.incomingServer.rootMsgFolder.getChildNamed( + "unread-local" + ); + let searchScope = + unreadLocal.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri"); + Assert.equal( + searchScope, + "mailbox://nobody@Local%20Folders/Inbox|mailbox://nobody@Local%20Folders/Trash" + ); + let invalidServer = + localAccountUtils.incomingServer.rootMsgFolder.getChildNamed( + "invalidserver-local" + ); + searchScope = + invalidServer.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri"); + Assert.equal(searchScope, "mailbox://nobody@Local%20Folders/Inbox"); + + let tagsFolder = + localAccountUtils.incomingServer.rootMsgFolder.getChildNamed("$label1"); + Assert.equal( + tagsFolder.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri"), + "*" + ); + Assert.equal( + tagsFolder.msgDatabase.dBFolderInfo.getCharProperty("searchStr"), + "AND (tag,contains,$label1)" + ); +} diff --git a/comm/mailnews/base/test/unit/test_mailServices.js b/comm/mailnews/base/test/unit/test_mailServices.js new file mode 100644 index 0000000000..1c8299b4f5 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_mailServices.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests for the MailServices module. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +add_task(function test_services() { + function check_service(service, serviceInterface) { + Assert.ok( + service in MailServices, + `${service} should be a member of MailServices` + ); + Assert.ok( + MailServices[service] instanceof serviceInterface, + `MailServices.${service} should implement Ci.${serviceInterface.name}` + ); + } + + check_service("mailSession", Ci.nsIMsgMailSession); + check_service("accounts", Ci.nsIMsgAccountManager); + check_service("pop3", Ci.nsIPop3Service); + check_service("imap", Ci.nsIImapService); + check_service("nntp", Ci.nsINntpService); + check_service("smtp", Ci.nsISmtpService); + check_service("compose", Ci.nsIMsgComposeService); + check_service("ab", Ci.nsIAbManager); + check_service("copy", Ci.nsIMsgCopyService); + check_service("mfn", Ci.nsIMsgFolderNotificationService); + check_service("headerParser", Ci.nsIMsgHeaderParser); + check_service("mimeConverter", Ci.nsIMimeConverter); + check_service("tags", Ci.nsIMsgTagService); + check_service("filters", Ci.nsIMsgFilterService); + check_service("junk", Ci.nsIJunkMailPlugin); +}); + +add_task(function test_message_services() { + function check_message_service(uri) { + let service = MailServices.messageServiceFromURI(uri); + Assert.ok( + service instanceof Ci.nsIMsgMessageService, + `message service for ${uri.substring( + 0, + uri.indexOf(":") + )} URIs should exist` + ); + } + + check_message_service("file://it.does.not.matter/"); + check_message_service("imap://it.does.not.matter/"); + check_message_service("imap-message://it.does.not.matter/"); + check_message_service("mailbox://it.does.not.matter/"); + check_message_service("mailbox-message://it.does.not.matter/"); + check_message_service("news://it.does.not.matter/"); + check_message_service("news-message://it.does.not.matter/"); + + Assert.throws( + () => MailServices.messageServiceFromURI("fake://not.going.to.work/"), + () => true, // Accept any exception. + "message service for other URIs should not exist" + ); +}); diff --git a/comm/mailnews/base/test/unit/test_mailstoreConverter.js b/comm/mailnews/base/test/unit/test_mailstoreConverter.js new file mode 100644 index 0000000000..f440b9c8cd --- /dev/null +++ b/comm/mailnews/base/test/unit/test_mailstoreConverter.js @@ -0,0 +1,376 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { convertMailStoreTo } = ChromeUtils.import( + "resource:///modules/mailstoreConverter.jsm" +); + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +// Test data for round-trip test. +let testEmails = [ + // Base64 encoded bodies. + "../../../data/01-plaintext.eml", + "../../../data/02-plaintext+attachment.eml", + "../../../data/03-HTML.eml", + "../../../data/04-HTML+attachment.eml", + "../../../data/05-HTML+embedded-image.eml", + "../../../data/06-plaintext+HMTL.eml", + "../../../data/07-plaintext+(HTML+embedded-image).eml", + "../../../data/08-plaintext+HTML+attachment.eml", + "../../../data/09-(HTML+embedded-image)+attachment.eml", + "../../../data/10-plaintext+(HTML+embedded-image)+attachment.eml", + + // Bodies with non-ASCII characters in UTF-8 and other charsets. + "../../../data/11-plaintext.eml", + "../../../data/12-plaintext+attachment.eml", // using ISO-8859-7 (Greek) + "../../../data/13-HTML.eml", + "../../../data/14-HTML+attachment.eml", + "../../../data/15-HTML+embedded-image.eml", + "../../../data/16-plaintext+HMTL.eml", // text part is base64 encoded + "../../../data/17-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded + "../../../data/18-plaintext+HTML+attachment.eml", + "../../../data/19-(HTML+embedded-image)+attachment.eml", + "../../../data/20-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252 + + // Bodies with non-ASCII characters in UTF-8 and other charsets, all encoded with quoted printable. + "../../../data/21-plaintext.eml", + "../../../data/22-plaintext+attachment.eml", // using ISO-8859-7 (Greek) + "../../../data/23-HTML.eml", + "../../../data/24-HTML+attachment.eml", + "../../../data/25-HTML+embedded-image.eml", + "../../../data/26-plaintext+HMTL.eml", // text part is base64 encoded + "../../../data/27-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded + "../../../data/28-plaintext+HTML+attachment.eml", + "../../../data/29-(HTML+embedded-image)+attachment.eml", + "../../../data/30-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252 +]; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + add_task(async function () { + await doMboxTest("test1", "../../../data/mbox_modern", 2); + await doMboxTest("test2", "../../../data/mbox_mboxrd", 2); + await doMboxTest("test3", "../../../data/mbox_unquoted", 2); + await roundTripTest(); + // Ideas for more tests: + // - check a really big mbox + // - check with really huge message (larger than one chunk) + // - check mbox with "From " line on chunk boundary + // - add tests for maildir->mbox conversion + // - check that conversions preserve message body (ie that the + // "From " line escaping scheme is reversible) + }); + + run_next_test(); +} + +/** + * Helper to create a server, account and inbox, and install an + * mbox file. + * + * @param {string} srvName - A unique server name to use for the test. + * @param {string} mboxFilename - mbox file to install and convert. + * @returns {nsIMsgIncomingServer} a server. + */ +function setupServer(srvName, mboxFilename) { + // {nsIMsgIncomingServer} pop server for the test. + let server = MailServices.accounts.createIncomingServer( + srvName, + "localhost", + "pop3" + ); + let account = MailServices.accounts.createAccount(); + account.incomingServer = server; + server.QueryInterface(Ci.nsIPop3IncomingServer); + server.valid = true; + + let inbox = account.incomingServer.rootFolder.getFolderWithFlags( + Ci.nsMsgFolderFlags.Inbox + ); + + // install the mbox file + let mboxFile = do_get_file(mboxFilename); + mboxFile.copyTo(inbox.filePath.parent, inbox.filePath.leafName); + + // TODO: is there some way to make folder rescan the mbox? + // We don't need it for this, but would be nice to do things properly. + return server; +} + +/** + * Perform an mbox->maildir conversion test. + * + * @param {string} srvName - A unique server name to use for the test. + * @param {string} mboxFilename - mbox file to install and convert. + * @param {number} expectCnt - Number of messages expected. + * @returns {nsIMsgIncomingServer} a server. + */ +async function doMboxTest(srvName, mboxFilename, expectCnt) { + // set up an account+server+inbox and copy in the test mbox file + let server = setupServer(srvName, mboxFilename); + + let mailstoreContractId = Services.prefs.getCharPref( + "mail.server." + server.key + ".storeContractID" + ); + + await convertMailStoreTo(mailstoreContractId, server, new EventTarget()); + + // Converted. Now find resulting Inbox/cur directory so + // we can count the messages there. + + let inbox = server.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox); + // NOTE: the conversion updates the path of the root folder, + // but _not_ the path of the inbox... + // Ideally, we'd just use inbox.filePath here, but + // instead we'll have compose the path manually. + + let curDir = server.rootFolder.filePath; + curDir.append(inbox.filePath.leafName); + curDir.append("cur"); + + // Sanity check. + Assert.ok(curDir.isDirectory(), "'cur' directory created"); + + // Check number of messages in Inbox/cur is what we expect. + let cnt = [...curDir.directoryEntries].length; + + Assert.equal( + cnt, + expectCnt, + "expected number of messages (" + mboxFilename + ")" + ); +} + +/** + * Create a temporary directory. The caller is responsible for deleting it. + * + * @param {string} prefix - Generated dir name will be of the form: + * "<prefix><random_sequence>". + * @returns {string} full path of new directory. + */ +async function tempDir(prefix) { + if (!prefix) { + prefix = ""; + } + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path; + while (true) { + let name = prefix + Math.floor(Math.random() * 0xffffffff).toString(16); + let fullPath = PathUtils.join(tmpDir, name); + try { + await IOUtils.makeDirectory(fullPath, { ignoreExisting: false }); + return fullPath; + } catch (e) { + // If directory already exists, try another name. Else bail out. + if ( + !(DOMException.isInstance(e) && e.name === "NoModificationAllowedError") + ) { + throw e; + } + } + } +} + +/** + * Test that messages survive unscathed in a roundtrip conversion, + * maildir -> mbox -> maildir. + * The final mailbox should have an identical set of files to the initial one, + * albeit with different filenames. + * Purely filesystem based. + * + * Would be nice to do a mbox->maildir->mbox roundtrip too, but that'd involve + * parsing the mbox files to compare them (can't just compare mbox files because + * message order and "From " lines can change). + */ +async function roundTripTest() { + // Set up initial maildir structure + let initialRoot = await tempDir("initial"); + + let inbox = PathUtils.join(initialRoot, "INBOX"); + await IOUtils.makeDirectory(inbox); + // Create a couple of subdirs under INBOX + let subdir = PathUtils.join(initialRoot, "INBOX.sbd"); + await IOUtils.makeDirectory(subdir); + let foodir = PathUtils.join(subdir, "foo"); + await IOUtils.makeDirectory(foodir); + let bardir = PathUtils.join(subdir, "bar"); + await IOUtils.makeDirectory(bardir); + + // Populate all the folders with some test emails. + const absolutePaths = testEmails.map(path => do_get_file(path).path); + await populateMaildir(inbox, absolutePaths); + await populateMaildir(foodir, absolutePaths); + await populateMaildir(bardir, absolutePaths); + + // Add a pick of "special" files, which should survive the trip verbatim. + for (let special of ["filterlog.html", "feeds.json", "rules.dat"]) { + let f = PathUtils.join(initialRoot, special); + await IOUtils.writeUTF8(f, f); // Use the filename for content. + } + + // Create root dirs for intermediate and final result. + let mboxRoot = await tempDir("mbox"); + let finalRoot = await tempDir("final"); + + // Convert: maildir -> mbox -> maildir + await doConvert("maildir", initialRoot, "mbox", mboxRoot); + await doConvert("mbox", mboxRoot, "maildir", finalRoot); + + // compare results - use checksums, because filenames will differ. + await recursiveMaildirCompare(initialRoot, finalRoot); +} + +/** + * Helper to adapt the callbacks from converterWorker into a promise. + * + * @param {string} srcType - type of source ("maildir", "mbox") + * @param {string} srcRoot - root directory containing the src folders. + * @param {string} destType - type of destination ("maildir", "mbox") + * @param {string} destRoot - root directory to place converted store. + * @returns {Promise} resolved when when conversion is complete. + */ +function doConvert(srcType, srcRoot, destType, destRoot) { + return new Promise(function (resolve, reject) { + let worker = new ChromeWorker("resource:///modules/converterWorker.js"); + worker.addEventListener("message", function (ev) { + if (ev.data.msg == "success") { + resolve(); + } + }); + worker.addEventListener("error", function (ev) { + reject(ev.message); + }); + // Go. + worker.postMessage({ + srcType, + destType, + srcRoot, + destRoot, + }); + }); +} + +/** + * Copy a list of email files (.eml) files into a maildir, creating "cur" + * and "tmp" subdirs if required. + * + * @param {string} maildir - Path to the maildir directory. + * @param {Array<string>} emailFiles - paths of source .eml files to copy. + */ +async function populateMaildir(maildir, emailFiles) { + let cur = PathUtils.join(maildir, "cur"); + await IOUtils.makeDirectory(cur); + await IOUtils.makeDirectory(PathUtils.join(maildir, "tmp")); + + // Normally maildir files would have a name derived from their msg-id field, + // but here we'll just use a timestamp-based one to save parsing them. + let ident = Date.now(); + for (let src of emailFiles) { + let dest = PathUtils.join(cur, ident.toString() + ".eml"); + ident += 1; + await IOUtils.copy(src, dest); + } +} + +/* + * List files in a directory (excludes subdirectories). + * + * @param {String} dirPath - Full path of directory. + * @returns {Array<String} full paths of the files. + */ +async function listFiles(dirPath) { + let files = []; + // Note: IOUtils has no dir iterator at time of writing. + for (const path of await IOUtils.getChildren(dirPath)) { + let fileInfo = await IOUtils.stat(path); + if (fileInfo.type !== "directory") { + files.push(path); + } + } + return files; +} + +/* + * Calculate md5 checksum for a file. + * + * @param {String} fileName - Full path to file. + * @returns {String} checksum of the file contents. + */ +async function md5Sum(fileName) { + let md5 = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); + md5.init(Ci.nsICryptoHash.MD5); + let raw = await IOUtils.read(fileName); + md5.update(raw, raw.byteLength); + return md5.finish(true); +} + +/** + * Compare all maildir directories in two directory trees. + * The comparison is per-maildir, by looking at the checksums of their emails. + * Asserts a test fail if any differences are found. + * + * @param {string} rootA - path to root of maildir store A. + * @param {string} rootB - path to root of maildir store B. + */ +async function recursiveMaildirCompare(rootA, rootB) { + let subdirs = []; + let maildirs = []; + let otherFiles = []; + for (let path of await IOUtils.getChildren(rootA)) { + let stat = await IOUtils.stat(path); + let name = PathUtils.filename(path); + if (stat.type === "directory") { + if (name.endsWith(".sbd")) { + subdirs.push(name); + } else { + // Assume all other dirs are maildirs. + maildirs.push(name); + } + } else { + otherFiles.push(name); + } + } + + // Compare the maildirs we found here. + let md5DirContents = async function (dirPath) { + let checksums = []; + for (let f of await listFiles(dirPath)) { + checksums.push(await md5Sum(f)); + } + return checksums; + }; + + for (let name of maildirs) { + let checksumsA = await md5DirContents(PathUtils.join(rootA, name, "cur")); + let checksumsB = await md5DirContents(PathUtils.join(rootB, name, "cur")); + + checksumsA.sort(); + checksumsB.sort(); + let match = checksumsA.length == checksumsB.length; + for (let i = 0; match && i < checksumsA.length; i++) { + match = checksumsA[i] == checksumsB[i]; + } + Assert.ok(match, "roundtrip preserves messages in maildir " + name); + } + + // Make sure any "special" files survived the trip intact. + for (let name of otherFiles) { + let checksumA = await md5Sum(PathUtils.join(rootA, name)); + let pathB = PathUtils.join(rootB, name); + let checksumB = (await IOUtils.exists(pathB)) ? await md5Sum(pathB) : null; + Assert.equal(checksumA, checksumB, "roundtrip preserves " + name); + } + + // Recurse down into .sbd dirs. + for (let name of subdirs) { + await recursiveMaildirCompare( + PathUtils.join(rootA, name), + PathUtils.join(rootB, name) + ); + } +} diff --git a/comm/mailnews/base/test/unit/test_mimemaltdetach.js b/comm/mailnews/base/test/unit/test_mimemaltdetach.js new file mode 100644 index 0000000000..1f90566726 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_mimemaltdetach.js @@ -0,0 +1,160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests nsIMessenger's detachAttachmentsWOPrompts of Mime multi-part + * alternative messages. + */ + +var { MsgHdrToMimeMessage } = ChromeUtils.import( + "resource:///modules/gloda/MimeMessage.jsm" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +function SaveAttachmentCallback() { + this.attachments = null; + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); +} + +SaveAttachmentCallback.prototype = { + callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) { + this.attachments = aMimeMessage.allAttachments; + this._resolve(); + }, + get promise() { + return this._promise; + }, +}; +var gCallbackObject = new SaveAttachmentCallback(); + +add_setup(async function () { + if (!localAccountUtils.inboxFolder) { + localAccountUtils.loadLocalMailAccount(); + } +}); + +add_task(async function startCopy() { + // Get a message into the local filestore. + let mailFile = do_get_file("../../../data/multipartmalt-detach"); + let listener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + mailFile, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + listener, + null + ); + await listener.promise; +}); + +// process the message through mime +add_task(async function startMime() { + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + + MsgHdrToMimeMessage( + msgHdr, + gCallbackObject, + gCallbackObject.callback, + true // allowDownload + ); + + await gCallbackObject.promise; +}); + +// detach any found attachments +add_task(async function startDetach() { + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey); + + let messenger = Cc["@mozilla.org/messenger;1"].createInstance( + Ci.nsIMessenger + ); + let attachment = gCallbackObject.attachments[0]; + let listener = new PromiseTestUtils.PromiseUrlListener(); + + messenger.detachAttachmentsWOPrompts( + do_get_profile(), + [attachment.contentType], + [attachment.url], + [attachment.name], + [msgURI], + listener + ); + await listener.promise; +}); + +// test that the detachment was successful +add_task(async function testDetach() { + // The message contained a file "head_update.txt" which should + // now exist in the profile directory. + let checkFile = do_get_profile().clone(); + checkFile.append("head_update.txt"); + Assert.ok(checkFile.exists()); + Assert.ok(checkFile.fileSize > 0); + + // The message should now have a detached attachment. Read the message, + // and search for "AttachmentDetached" which is added on detachment. + + // Get the message header + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + + let messageContent = await getContentFromMessage(msgHdr); + Assert.ok(messageContent.includes("AttachmentDetached")); + // Make sure the body survived the detach. + Assert.ok(messageContent.includes("body hello")); +}); + +/** + * Get the full message content. + * + * @param {nsIMsgDBHdr} aMsgHdr - Message object whose text body will be read. + * @returns {Promise<string>} full message contents. + */ +function getContentFromMessage(aMsgHdr) { + let msgFolder = aMsgHdr.folder; + let msgUri = msgFolder.getUriForMsg(aMsgHdr); + + return new Promise((resolve, reject) => { + let streamListener = { + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ), + content: "", + onDataAvailable(request, inputStream, offset, count) { + this.sis.init(inputStream); + this.content += this.sis.read(count); + }, + onStartRequest(request) {}, + onStopRequest(request, statusCode) { + this.sis.close(); + if (Components.isSuccessCode(statusCode)) { + resolve(this.content); + } else { + reject(new Error(statusCode)); + } + }, + }; + MailServices.messageServiceFromURI(msgUri).streamMessage( + msgUri, + streamListener, + null, + null, + false, + "", + false + ); + }); +} diff --git a/comm/mailnews/base/test/unit/test_newMailNotification.js b/comm/mailnews/base/test/unit/test_newMailNotification.js new file mode 100644 index 0000000000..d6e111d86a --- /dev/null +++ b/comm/mailnews/base/test/unit/test_newMailNotification.js @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tests for platform-independent code to count new and unread messages and pass the + * information to platform-specific notification modules */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/** + * Register listener for a particular event, make sure it shows up in the right lists + * of listeners (and not the wrong ones) and doesn't show up after being removed + */ +add_test(function testListeners() { + let notif = MailServices.newMailNotification.wrappedJSObject; + let listener = { onCountChanged: () => {} }; + + notif.addListener(listener, Ci.mozINewMailNotificationService.count); + let list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count); + Assert.equal(list.length, 1); + Assert.equal(list[0], listener); + + let newlist = notif.listenersForFlag( + Ci.mozINewMailNotificationService.messages + ); + Assert.equal(newlist.length, 0); + + notif.removeListener(listener); + list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count); + Assert.equal(list.length, 0); + + run_next_test(); +}); + +/* + * Register a listener for two types and another for one type, make sure they show up, + * remove one and make sure the other stays put + */ +add_test(function testMultiListeners() { + let notif = MailServices.newMailNotification.wrappedJSObject; + let l1 = { onCountChanged: () => {} }; + let l2 = { b: 2 }; + + notif.addListener( + l1, + Ci.mozINewMailNotificationService.count | + Ci.mozINewMailNotificationService.messages + ); + // do_check_eq(notif._listeners.length, 1); + notif.addListener(l2, Ci.mozINewMailNotificationService.messages); + // do_check_eq(notif._listeners.length, 2); + let list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count); + Assert.equal(list.length, 1); + Assert.equal(list[0], l1); + + let newlist = notif.listenersForFlag( + Ci.mozINewMailNotificationService.messages + ); + Assert.equal(newlist.length, 2); + + notif.removeListener(l1); + list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count); + Assert.equal(list.length, 0); + newlist = notif.listenersForFlag(Ci.mozINewMailNotificationService.messages); + Assert.equal(newlist.length, 1); + Assert.equal(newlist[0], l2); + notif.removeListener(l2); + + run_next_test(); +}); + +/* Make sure we get a notification call when the unread count changes on an Inbox */ +add_test(function testNotifyInbox() { + let notified = false; + let count = 0; + let mockListener = { + onCountChanged: function TNU_onCountChanged(updatedCount) { + notified = true; + count = updatedCount; + }, + }; + let folder = { + URI: "Test Inbox", + flags: Ci.nsMsgFolderFlags.Mail | Ci.nsMsgFolderFlags.Inbox, + }; + + const notificationService = MailServices.newMailNotification.wrappedJSObject; + + // Set up the notification service to start with a non-zero unread count to + // verify this value is correctly passed to new listeners. Do this before any + // listeners are added. + const startCount = 3; + notificationService.unreadCount = startCount; + + // Add a listener for count updates. + notificationService.addListener( + mockListener, + Ci.mozINewMailNotificationService.count + ); + + // Verify that a new listener is notified of the current count. + Assert.ok(notified, "New listeners should be notified of count when added."); + Assert.equal( + count, + startCount, + "New listener notification should contain the current unread count." + ); + + // Verify that listeners are notified of subsequent changes. + notified = false; + const updatedInboxCount = 5; + notificationService.onFolderIntPropertyChanged( + folder, + "TotalUnreadMessages", + startCount, + updatedInboxCount + ); + Assert.ok( + notified, + "Listeners should be notified of changes in inbox unread count." + ); + Assert.equal( + count, + updatedInboxCount, + "Notification should contain updated inbox unread count." + ); + + // Sanity check. + Assert.ok( + Services.prefs.getBoolPref("mail.notification.count.inbox_only", false), + "`inbox_only` pref should be true for test." + ); + + // Verify that listeners are not notified of changes outside of the inbox. + let nonInbox = { + URI: "Test Non-Inbox", + flags: Ci.nsMsgFolderFlags.Mail, + }; + notified = false; + notificationService.onFolderIntPropertyChanged( + nonInbox, + "TotalUnreadMessages", + 0, + 2 + ); + Assert.ok( + !notified, + "Listeners should not be notified of changes in unread count outside of inbox by default." + ); + Assert.equal( + count, + updatedInboxCount, + "Total unread message count should not have changed." + ); + + // Verify that, when `inbox_only` is false, unread messages outside of the + // inbox are counted. + Services.prefs.setBoolPref("mail.notification.count.inbox_only", false); + notified = false; + const updatedNonInboxCount = 2; + const updatedTotalCount = updatedInboxCount + updatedNonInboxCount; + notificationService.onFolderIntPropertyChanged( + nonInbox, + "TotalUnreadMessages", + 0, + updatedNonInboxCount + ); + Assert.ok( + notified, + "Listeners should be notified of changes in unread count outside of inbox when pref is set." + ); + Assert.equal( + count, + updatedTotalCount, + "Notification should contain total unread count for all counted folders." + ); + + // Verify that listeners are never informed of updates in special folders. + let special = { + URI: "Test Special", + flags: Ci.nsMsgFolderFlags.Mail | Ci.nsMsgFolderFlags.Junk, + }; + notified = false; + notificationService.onFolderIntPropertyChanged( + special, + "TotalUnreadMessages", + 0, + 2 + ); + Assert.ok( + !notified, + "Listeners should not be notified of changes in special folder unread count." + ); + Assert.equal( + count, + updatedTotalCount, + "Total unread message count should not have changed." + ); + + run_next_test(); +}); diff --git a/comm/mailnews/base/test/unit/test_nsIFolderListener.js b/comm/mailnews/base/test/unit/test_nsIFolderListener.js new file mode 100644 index 0000000000..aa1b19ecfb --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIFolderListener.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that adding nsIFolderListener in js does not cause any crash. + */ + +var { MessageGenerator } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); + +var folderListener = { + onFolderAdded() {}, + onMessageAdded() {}, + onFolderRemoved() {}, + onMessageRemoved() {}, + onFolderPropertyChanged() {}, + onFolderIntPropertyChanged() {}, + onFolderBoolPropertyChanged() {}, + onFolderUnicharPropertyChanged() {}, + onFolderPropertyFlagChanged() {}, + onFolderEvent() {}, +}; + +var targetFolder; +var messageInjection; + +add_setup(async function () { + let msgGen = new MessageGenerator(); + messageInjection = new MessageInjection({ mode: "local" }, msgGen); + + targetFolder = await messageInjection.makeEmptyFolder(); + targetFolder.AddFolderListener(folderListener); + registerCleanupFunction(function () { + targetFolder.RemoveFolderListener(folderListener); + }); +}); + +add_task(async function create_new_message() { + await messageInjection.makeNewSetsInFolders([targetFolder], [{ count: 1 }]); +}); diff --git a/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js b/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js new file mode 100644 index 0000000000..de49d5b7eb --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js @@ -0,0 +1,68 @@ +/* -*- mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsIMsgContentPolicy to check we could add/remove customized protocol to + * nsMsgContentPolicy. + */ +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +function makeURI(aURL) { + return Services.io.newURI(aURL); +} + +function run_test() { + var content_policy = Cc["@mozilla.org/messenger/content-policy;1"].getService( + Ci.nsIContentPolicy + ); + + Assert.ok(content_policy); + + var msg_content_policy = content_policy.QueryInterface( + Ci.nsIMsgContentPolicy + ); + + Assert.ok(msg_content_policy); + + var req_uri = makeURI("custom-scheme://custom_url/1.emal"); + Assert.ok(req_uri); + + var content_uri = makeURI("custom-scheme://custom_content_url/1.jsp"); + Assert.ok(content_uri); + + let tmpChannel = NetUtil.newChannel({ + uri: content_uri, + // Needs one of 'loadingNode', 'loadingPrincipal' or 'loadUsingSystemPrincipal' which we don't have. + // Even with `loadUsingSystemPrincipal: true` this fails with "unknown protocol". See bug 1446587. + securityFlags: Ci.nsILoadInfo.SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, + contentPolicyType: Ci.nsIContentPolicy.TYPE_IMAGE, + }); + let tmpLoadInfo = tmpChannel.loadInfo; + + var decision = content_policy.shouldLoad( + content_uri, + tmpLoadInfo, + "img/jpeg" + ); + Assert.notEqual( + decision, + Ci.nsIContentPolicy.ACCEPT, + "customized protocol should not load" + ); + + msg_content_policy.addExposedProtocol("custom-scheme"); + + decision = content_policy.shouldLoad(content_uri, tmpLoadInfo, "img/jpeg"); + Assert.equal( + decision, + Ci.nsIContentPolicy.ACCEPT, + "customized protocol should load" + ); + + msg_content_policy.removeExposedProtocol("custom-scheme"); + + decision = content_policy.shouldLoad(content_uri, tmpLoadInfo, "img/jpeg"); + Assert.notEqual( + decision, + Ci.nsIContentPolicy.ACCEPT, + "customized protocol should not load" + ); +} diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolder.js b/comm/mailnews/base/test/unit/test_nsIMsgFolder.js new file mode 100644 index 0000000000..9cfb57a07c --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgFolder.js @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsIMsgFolder functions. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // Create a local mail account (we need this first) + MailServices.accounts.createLocalMailAccount(); + + // Get the account + let account = MailServices.accounts.accounts[0]; + + // Get the root folder + var root = account.incomingServer.rootFolder; + + // Add a sub folder to ensure that we have some folders created + root.createSubfolder("folder1", null); + + // Test - getChildNamed + + var caught = false; + try { + root.getChildNamed("folder"); + } catch (e) { + caught = true; + } + Assert.equal(caught, true); + + caught = false; + try { + root.getChildNamed("Trash1"); + } catch (e) { + caught = true; + } + Assert.equal(caught, true); + + var folder1 = root.getChildNamed("folder1"); + + Assert.notEqual(folder1, folder2); + Assert.equal(folder1.prettyName, "folder1"); + + var folder2 = root.getChildNamed("FOLDER1"); + + Assert.equal(folder1, folder2); + + // Check special folders aren't deletable, and that normal folders are. + if (!root.containsChildNamed("Inbox")) { + root.createSubfolder("Inbox", null); + } + var inbox = root.getChildNamed("Inbox"); + inbox.setFlag(Ci.nsMsgFolderFlags.Inbox); + Assert.ok(!inbox.deletable); + + if (!root.containsChildNamed("Drafts")) { + root.createSubfolder("Drafts", null); + } + var drafts = root.getChildNamed("Drafts"); + drafts.setFlag(Ci.nsMsgFolderFlags.Drafts); + Assert.ok(!drafts.deletable); + + if (!root.containsChildNamed("Templates")) { + root.createSubfolder("Templates", null); + } + var templates = root.getChildNamed("Templates"); + templates.setFlag(Ci.nsMsgFolderFlags.Templates); + Assert.ok(!templates.deletable); + + if (!root.containsChildNamed("Sent")) { + root.createSubfolder("Sent", null); + } + var sent = root.getChildNamed("Sent"); + sent.setFlag(Ci.nsMsgFolderFlags.SentMail); + Assert.ok(!sent.deletable); + + if (!root.containsChildNamed("Archives")) { + root.createSubfolder("Archives", null); + } + var archives = root.getChildNamed("Archives"); + archives.setFlag(Ci.nsMsgFolderFlags.Archive); + Assert.ok(!archives.deletable); + + if (!root.containsChildNamed("Trash")) { + root.createSubfolder("Trash", null); + } + var trash = root.getChildNamed("Trash"); + trash.setFlag(Ci.nsMsgFolderFlags.Trash); + Assert.ok(!trash.deletable); + + if (!root.containsChildNamed("Outbox")) { + root.createSubfolder("Outbox", null); + } + var outbox = root.getChildNamed("Outbox"); + outbox.setFlag(Ci.nsMsgFolderFlags.Queue); + Assert.ok(!outbox.deletable); + + // test a normal folder is deletable + Assert.ok(folder1.deletable); +} diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js new file mode 100644 index 0000000000..9fd9688b53 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js @@ -0,0 +1,228 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Sanity checks for nsIMsgFolderCache/nsIMsgFolderCacheElement. + */ +add_task(function test_basics() { + let profileDir = do_get_profile(); + let jsonFile = profileDir.clone(); + jsonFile.append("folderCache.json"); + let legacyFile = profileDir.clone(); + legacyFile.append("panacea.dat"); + + // Create an empty cache object and start poking it. + { + let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance( + Ci.nsIMsgFolderCache + ); + // Neither of these files exist, and that's fine. + Assert.ok(!jsonFile.exists()); + Assert.ok(!legacyFile.exists()); + cache.init(jsonFile, legacyFile); + + // getCacheElement has to be told to create non-existent keys. + Assert.throws(function () { + cache.getCacheElement("a/non/existent/key", false); + }, /NS_ERROR_NOT_AVAILABLE/); + let e1 = cache.getCacheElement("/made/up/path/Inbox", true); + + // Can set, get and modify Int32 values? + e1.setCachedInt32("wibble", -1); + Assert.equal(e1.getCachedInt32("wibble"), -1); + e1.setCachedInt32("wibble", 42); + Assert.equal(e1.getCachedInt32("wibble"), 42); + + // Check some allowed conversions from Int32. + Assert.equal(e1.getCachedUInt32("wibble"), 42); + Assert.equal(e1.getCachedInt64("wibble"), 42); + Assert.equal(e1.getCachedString("wibble"), "42"); + + // Can set, get and modify UInt32 values? + e1.setCachedUInt32("pibble", 0xffffffff); + Assert.equal(e1.getCachedUInt32("pibble"), 0xffffffff); + e1.setCachedUInt32("pibble", 42); + Assert.equal(e1.getCachedUInt32("pibble"), 42); + + // Check some allowed conversions from UInt32. + Assert.equal(e1.getCachedInt32("pibble"), 42); + Assert.equal(e1.getCachedInt64("pibble"), 42); + Assert.equal(e1.getCachedString("pibble"), "42"); + + // Can set, get and modify Int64 values? + e1.setCachedInt64("foo", 2305843009213694000); + Assert.equal(e1.getCachedInt64("foo"), 2305843009213694000); + e1.setCachedInt64("foo", -2305843009213694000); + Assert.equal(e1.getCachedInt64("foo"), -2305843009213694000); + e1.setCachedInt64("foo", 42); + Assert.equal(e1.getCachedInt64("foo"), 42); + + // Check some allowed conversions from Int64. + Assert.equal(e1.getCachedInt32("foo"), 42); + Assert.equal(e1.getCachedUInt32("foo"), 42); + Assert.equal(e1.getCachedString("foo"), "42"); + + // Can set, get and modify String values? + e1.setCachedString("bar", "Before"); + Assert.equal(e1.getCachedString("bar"), "Before"); + e1.setCachedString("bar", "After"); + Assert.equal(e1.getCachedString("bar"), "After"); + e1.setCachedString("bar", "日本語"); + Assert.equal(e1.getCachedString("bar"), "日本語"); + + // Check some disallowed conversions from String. + Assert.throws(function () { + e1.getCachedInt32("bar"); + }, /NS_ERROR_NOT_AVAILABLE/); + Assert.throws(function () { + e1.getCachedUInt32("bar"); + }, /NS_ERROR_NOT_AVAILABLE/); + Assert.throws(function () { + e1.getCachedInt64("bar"); + }, /NS_ERROR_NOT_AVAILABLE/); + + // Trying to read missing properties is an error. + Assert.throws(function () { + e1.getCachedInt32("non-existent-property"); + }, /NS_ERROR_NOT_AVAILABLE/); + Assert.throws(function () { + e1.getCachedUInt32("non-existent-property"); + }, /NS_ERROR_NOT_AVAILABLE/); + Assert.throws(function () { + e1.getCachedInt64("non-existent-property"); + }, /NS_ERROR_NOT_AVAILABLE/); + Assert.throws(function () { + e1.getCachedString("non-existent-property"); + }, /NS_ERROR_NOT_AVAILABLE/); + + // Force a save to jsonFile. The changes we made will have queued up a + // cache autosave but we don't want to wait that long. The cache dtor + // would also save, but we don't want to second-guess JS garbage + // collection here. + cache.flush(); + } + + // Create a new cache object, reload jsonFile and make sure all the expected + // values are there. + { + let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance( + Ci.nsIMsgFolderCache + ); + // jsonFile is there now. + Assert.ok(jsonFile.exists()); + Assert.ok(!legacyFile.exists()); + cache.init(jsonFile, legacyFile); + // Make sure all the values we previously set are intact. + let e1 = cache.getCacheElement("/made/up/path/Inbox", true); + Assert.equal(e1.getCachedInt32("wibble"), 42); + Assert.equal(e1.getCachedUInt32("pibble"), 42); + Assert.equal(e1.getCachedInt64("foo"), 42); + Assert.equal(e1.getCachedString("bar"), "日本語"); + } + + // clean up for next test + jsonFile.remove(false); +}); + +add_task(async function test_null_entries() { + // Write out a trivial foldercache file with a null value. + let data = { "a-folder-key": { foo: null } }; + let jsonFilename = PathUtils.join(PathUtils.tempDir, "foo.json"); + await IOUtils.writeJSON(jsonFilename, data); + + // Load it into an msIMsgFolderCache + let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance( + Ci.nsIMsgFolderCache + ); + let jsonFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + jsonFile.initWithPath(jsonFilename); + let morkFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + morkFile.initWithPath( + PathUtils.join(PathUtils.tempDir, "non-existent-file.dat") + ); + cache.init(jsonFile, morkFile); + + // + let e1 = cache.getCacheElement("a-folder-key", false); + + // Make sure all accessors convert the null appropriately. + Assert.equal(e1.getCachedInt32("foo"), 0); + Assert.equal(e1.getCachedUInt32("foo"), 0); + Assert.equal(e1.getCachedInt64("foo"), 0); + Assert.equal(e1.getCachedString("foo"), ""); +}); + +/** + * Test foldercache migration from mork DB (panacea.dat) to JSON. + */ +add_task(async function test_migration() { + let profileDir = do_get_profile(); + let jsonFile = profileDir.clone(); + jsonFile.append("folderCache.json"); + let legacyFile = profileDir.clone(); + legacyFile.append("panacea.dat"); + + Assert.ok(!jsonFile.exists()); + Assert.ok(!legacyFile.exists()); + + // Install our test legacy file. + do_get_file("data/panacea.dat").copyTo(profileDir, legacyFile.leafName); + + // Set up the cache. + { + let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance( + Ci.nsIMsgFolderCache + ); + cache.init(jsonFile, legacyFile); + + // Migration should have occurred. + Assert.ok(jsonFile.exists()); + Assert.ok(!legacyFile.exists()); + + // Done with the cache now. + } + + // Compare the migrated json to the json we expect. + let raw = await IOUtils.readUTF8(jsonFile.path); + let got = JSON.parse(raw); + + raw = await IOUtils.readUTF8(do_get_file("data/folderCache.json").path); + let expect = JSON.parse(raw); + + Assert.deepEqual(got, expect); + + // clean up for next test + jsonFile.remove(false); +}); + +/** + * Test foldercache migration doesn't crash with a dud panacea.dat. + */ +add_task(async function test_bad_pancea_dat() { + let profileDir = do_get_profile(); + let jsonFile = profileDir.clone(); + jsonFile.append("folderCache.json"); + let legacyFile = profileDir.clone(); + legacyFile.append("panacea.dat"); + + Assert.ok(!jsonFile.exists()); + Assert.ok(!legacyFile.exists()); + + // Install our bad panacea.dat. It has only the first line - the mork magic + // cookie - so it's valid enough for mork to open, but doesn't have + // anything the migration is looking for. + do_get_file("data/panacea_empty.dat").copyTo(profileDir, legacyFile.leafName); + + // Set up the cache. + let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance( + Ci.nsIMsgFolderCache + ); + // init() returns OK even if migration fails - the show must go on! + cache.init(jsonFile, legacyFile); + + // If we get this far, we didn't crash, which is good. + // The migration should have left everything as it was. + Assert.ok(legacyFile.exists()); + Assert.ok(!jsonFile.exists()); +}); diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js new file mode 100644 index 0000000000..c0a9b72e64 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Test suite for basic functionality with nsIMsgFolderListeners. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var nsIMFNService = Ci.nsIMsgFolderNotificationService; + +var gIndividualFlags = [ + nsIMFNService.msgAdded, + nsIMFNService.msgsClassified, + nsIMFNService.msgsJunkStatusChanged, + nsIMFNService.msgsDeleted, + nsIMFNService.msgsMoveCopyCompleted, + nsIMFNService.msgKeyChanged, + nsIMFNService.msgUnincorporatedMoved, + nsIMFNService.folderAdded, + nsIMFNService.folderDeleted, + nsIMFNService.folderMoveCopyCompleted, + nsIMFNService.folderRenamed, + nsIMFNService.folderCompactStart, + nsIMFNService.folderCompactFinish, + nsIMFNService.folderReindexTriggered, +]; + +// Our listener, which captures events. +function gMFListener() {} +gMFListener.prototype = { + mReceived: 0, + mRemoveSelf: false, + msgAdded(aMsg) { + Assert.equal(this.mReceived & nsIMFNService.msgAdded, 0); + this.mReceived |= nsIMFNService.msgAdded; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgsClassified(aMsgs, aJunkProcessed, aTraitProcessed) { + Assert.equal(this.mReceived & nsIMFNService.msgsClassified, 0); + this.mReceived |= nsIMFNService.msgsClassified; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgsJunkStatusChanged(messages) { + Assert.equal(this.mReceived & nsIMFNService.msgsJunkStatusChanged, 0); + this.mReceived |= nsIMFNService.msgsJunkStatusChanged; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgsDeleted(aMsgs) { + Assert.equal(this.mReceived & nsIMFNService.msgsDeleted, 0); + this.mReceived |= nsIMFNService.msgsDeleted; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgsMoveCopyCompleted(aMove, aSrcMsgs, aDestFolder, aDestMsgs) { + Assert.equal(this.mReceived & nsIMFNService.msgsMoveCopyCompleted, 0); + this.mReceived |= nsIMFNService.msgsMoveCopyCompleted; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgKeyChanged(aOldMsgKey, aNewMsgHdr) { + Assert.equal(this.mReceived & nsIMFNService.msgKeyChanged, 0); + this.mReceived |= nsIMFNService.msgKeyChanged; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + msgUnincorporatedMoved(srcFolder, msg) { + Assert.equal(this.mReceived & nsIMFNService.msgUnincorporatedMoved, 0); + this.mReceived |= nsIMFNService.msgUnincorporatedMoved; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderAdded(aFolder) { + Assert.equal(this.mReceived & nsIMFNService.folderAdded, 0); + this.mReceived |= nsIMFNService.folderAdded; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderDeleted(aFolder) { + Assert.equal(this.mReceived & nsIMFNService.folderDeleted, 0); + this.mReceived |= nsIMFNService.folderDeleted; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderMoveCopyCompleted(aMove, aSrcFolder, aDestFolder) { + Assert.equal(this.mReceived & nsIMFNService.folderMoveCopyCompleted, 0); + this.mReceived |= nsIMFNService.folderMoveCopyCompleted; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderRenamed(aOrigFolder, aNewFolder) { + Assert.equal(this.mReceived & nsIMFNService.folderRenamed, 0); + this.mReceived |= nsIMFNService.folderRenamed; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderCompactStart(folder) { + Assert.equal(this.mReceived & nsIMFNService.folderCompactStart, 0); + this.mReceived |= nsIMFNService.folderCompactStart; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderCompactFinish(folder) { + Assert.equal(this.mReceived & nsIMFNService.folderCompactFinish, 0); + this.mReceived |= nsIMFNService.folderCompactFinish; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, + + folderReindexTriggered(folder) { + Assert.equal(this.mReceived & nsIMFNService.folderReindexTriggered, 0); + this.mReceived |= nsIMFNService.folderReindexTriggered; + if (this.mRemoveSelf) { + MailServices.mfn.removeListener(this); + } + }, +}; + +function NotifyMsgFolderListeners() { + MailServices.mfn.notifyMsgAdded(null); + MailServices.mfn.notifyMsgsClassified([], null, null); + MailServices.mfn.notifyMsgsJunkStatusChanged([]); + MailServices.mfn.notifyMsgsDeleted([]); + MailServices.mfn.notifyMsgsMoveCopyCompleted(null, [], null, []); + MailServices.mfn.notifyMsgKeyChanged(null, null); + MailServices.mfn.notifyMsgUnincorporatedMoved(null, null); + MailServices.mfn.notifyFolderAdded(null); + MailServices.mfn.notifyFolderDeleted(null); + MailServices.mfn.notifyFolderMoveCopyCompleted(null, null, null); + MailServices.mfn.notifyFolderRenamed(null, null); + MailServices.mfn.notifyFolderCompactStart(null); + MailServices.mfn.notifyFolderCompactFinish(null); + MailServices.mfn.notifyFolderReindexTriggered(null); +} + +function run_test() { + // Test: Add listeners + var singleListeners = []; + + var addAListener = function (flag) { + var listener = new gMFListener(); + MailServices.mfn.addListener(listener, flag); + singleListeners.push(listener); + }; + + gIndividualFlags.forEach(addAListener); + + // Test: Notify the listeners of all events. + NotifyMsgFolderListeners(); + + // Test: check whether the correct number of notifications have been received. + // Then remove the listeners + var checkFlag = function (flag) { + var listener = singleListeners.shift(); + Assert.equal(listener.mReceived, flag); + listener.mRemoveSelf = true; + listener.mReceived = 0; + singleListeners.push(listener); + }; + gIndividualFlags.forEach(checkFlag); + + // We'll do one more set of notifications, and remove ourselves in the middle of them + NotifyMsgFolderListeners(); + + // Test: all listeners should be removed at this point + Assert.ok(!MailServices.mfn.hasListeners); + + // Test: Send notifications again. Check that we don't receive any notifications. + singleListeners.forEach(function (listener) { + listener.mReceived = 0; + }); + + NotifyMsgFolderListeners(); + + var checkNotReceived = function () { + Assert.equal(singleListeners.shift().mReceived, 0); + }; + gIndividualFlags.forEach(checkNotReceived); +} diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js new file mode 100644 index 0000000000..aadbd75b8a --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js @@ -0,0 +1,444 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Test suite for nsIMsgFolderListener events due to local mail folder + * operations. + * + * Currently tested: + * - Adding new folders + * - Copy messages from files into the db + * - Moving and copying one or more messages from one local folder to another + * - Moving folders, with and without subfolders + * - Renaming folders + * - Deleting messages and folders, to trash and from trash (permanently) + */ + +/* import-globals-from ../../../test/resources/msgFolderListenerSetup.js */ +load("../../../resources/msgFolderListenerSetup.js"); + +// Globals +var gMsgFile1, gMsgFile2, gMsgFile3; +var gRootFolder; +var gLocalFolder2; +var gLocalFolder3; +var gLocalTrashFolder; + +// storeIn takes a string containing the variable to store the new folder in +function addFolder(parent, folderName, storeIn) { + gExpectedEvents = [ + [MailServices.mfn.folderAdded, parent, folderName, storeIn], + ]; + // We won't receive a copy listener notification for this + gCurrStatus |= kStatus.onStopCopyDone; + parent.createSubfolder(folderName, null); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +/** + * This will introduce a new message to the system which will generate an added + * notification and subsequently a classification notification. For the + * classification because no messages have yet been marked as junk and there + * are no traits configured, aJunkProcessed and aTraitProcessed will be false. + */ +function copyFileMessage(file, destFolder, isDraftOrTemplate) { + copyListener.mFolderStoredIn = destFolder; + gExpectedEvents = [ + [MailServices.mfn.msgAdded, gHdrsReceived], + [MailServices.mfn.msgsClassified, gHdrsReceived, false, false], + ]; + MailServices.copy.copyFileMessage( + file, + destFolder, + null, + isDraftOrTemplate, + 0, + "", + copyListener, + null + ); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function copyMessages(items, isMove, srcFolder, destFolder) { + gExpectedEvents = [ + [MailServices.mfn.msgsMoveCopyCompleted, isMove, items, destFolder, true], + ]; + MailServices.copy.copyMessages( + srcFolder, + items, + destFolder, + isMove, + copyListener, + null, + true + ); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function copyFolder(srcFolder, isMove, destFolder) { + gExpectedEvents = [ + [MailServices.mfn.folderMoveCopyCompleted, isMove, [srcFolder], destFolder], + ]; + MailServices.copy.copyFolder( + srcFolder, + destFolder, + isMove, + copyListener, + null + ); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function deleteMessages(srcFolder, items, deleteStorage, isMove) { + // We should only get the delete notification only if we are not moving, and are deleting from + // the storage/trash. We should get only the move/copy notification if we aren't. + var isTrashFolder = srcFolder.getFlag(Ci.nsMsgFolderFlags.Trash); + if (!isMove && (deleteStorage || isTrashFolder)) { + // We won't be getting any OnStopCopy notification in this case + gCurrStatus = kStatus.onStopCopyDone; + gExpectedEvents = [[MailServices.mfn.msgsDeleted, items]]; + } else { + // We have to be getting a move notification, even if isMove is false + gExpectedEvents = [ + [ + MailServices.mfn.msgsMoveCopyCompleted, + true, + items, + gLocalTrashFolder, + true, + ], + ]; + } + + srcFolder.deleteMessages( + items, + null, + deleteStorage, + isMove, + copyListener, + true + ); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function renameFolder(folder, newName) { + gExpectedEvents = [[MailServices.mfn.folderRenamed, [folder], newName]]; + gCurrStatus = kStatus.onStopCopyDone; + folder.rename(newName, null); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function deleteFolder(folder, child) { + // We won't be getting any OnStopCopy notification at all + // XXX delete to trash should get one, but we'll need to pass the listener + // somehow to deleteSelf + gCurrStatus = kStatus.onStopCopyDone; + // If ancestor is trash, expect a folderDeleted, otherwise expect + // a folderMoveCopyCompleted. + if (gLocalTrashFolder.isAncestorOf(folder)) { + if (child) { + gExpectedEvents = [ + [MailServices.mfn.folderDeleted, [child]], + [MailServices.mfn.folderDeleted, [folder]], + ]; + } else { + gExpectedEvents = [[MailServices.mfn.folderDeleted, [folder]]]; + } + } else { + gExpectedEvents = [ + [ + MailServices.mfn.folderMoveCopyCompleted, + true, + [folder], + gLocalTrashFolder, + ], + ]; + } + + folder.deleteSelf(null); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +function compactFolder(folder) { + gExpectedEvents = [ + [MailServices.mfn.folderCompactStart, folder], + [MailServices.mfn.folderCompactFinish, folder], + ]; + // We won't receive a copy listener notification for this + gCurrStatus |= kStatus.onStopCopyDone; + folder.compact(null, null); + gCurrStatus |= kStatus.functionCallDone; + if (gCurrStatus == kStatus.everythingDone) { + resetStatusAndProceed(); + } +} + +/* + * TESTS + */ + +// Beware before commenting out a test -- later tests might just depend on earlier ones +var gTestArray = [ + // Adding folders + // Create another folder to move and copy messages around, and force initialization. + function addFolder1() { + addFolder(gRootFolder, "folder2", function (folder) { + gLocalFolder2 = folder; + }); + }, + // Create a third folder for more testing. + function addFolder2() { + addFolder(gRootFolder, "folder3", function (folder) { + gLocalFolder3 = folder; + }); + }, + // Folder structure is now + // Inbox + // Trash + // folder2 + // folder3 + // Copying messages from files + function testCopyFileMessage1() { + copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false); + }, + function testCopyFileMessage2() { + copyFileMessage(gMsgFile2, localAccountUtils.inboxFolder, false); + }, + function testCopyFileMessage3() { + copyFileMessage(gMsgFile3, localAccountUtils.inboxFolder, true); + }, + + // Moving/copying messages + function testCopyMessages1() { + copyMessages( + [gMsgHdrs[0].hdr], + false, + localAccountUtils.inboxFolder, + gLocalFolder2 + ); + }, + function testCopyMessages2() { + copyMessages( + [gMsgHdrs[1].hdr, gMsgHdrs[2].hdr], + false, + localAccountUtils.inboxFolder, + gLocalFolder2 + ); + }, + function testMoveMessages1() { + copyMessages( + [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr], + true, + localAccountUtils.inboxFolder, + gLocalFolder3 + ); + }, + function testMoveMessages2() { + copyMessages( + [gMsgHdrs[2].hdr], + true, + localAccountUtils.inboxFolder, + gLocalTrashFolder + ); + }, + function testMoveMessages3() { + // This is to test whether the notification is correct for moving from trash + gMsgHdrs[2].hdr = gLocalTrashFolder.msgDatabase.getMsgHdrForMessageID( + gMsgHdrs[2].ID + ); + copyMessages([gMsgHdrs[2].hdr], true, gLocalTrashFolder, gLocalFolder3); + }, + // Moving/copying folders + function testCopyFolder1() { + copyFolder(gLocalFolder3, false, gLocalFolder2); + }, + function testMoveFolder1() { + copyFolder(gLocalFolder3, true, localAccountUtils.inboxFolder); + }, + function testMoveFolder2() { + copyFolder(gLocalFolder2, true, localAccountUtils.inboxFolder); + }, + // Folder structure should now be + // Inbox + // -folder2 + // --folder3 + // -folder3 + // Trash + + // Deleting messages + function testDeleteMessages1() { + // delete to trash + // Let's take a moment to re-initialize stuff that got moved + gLocalFolder2 = localAccountUtils.inboxFolder.getChildNamed("folder2"); + gLocalFolder3 = gLocalFolder2.getChildNamed("folder3"); + var folder3DB = gLocalFolder3.msgDatabase; + for (var i = 0; i < gMsgHdrs.length; i++) { + gMsgHdrs[i].hdr = folder3DB.getMsgHdrForMessageID(gMsgHdrs[i].ID); + } + + // Now delete the message + deleteMessages( + gLocalFolder3, + [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr], + false, + false + ); + }, + // shift delete + function testDeleteMessages2() { + deleteMessages(gLocalFolder3, [gMsgHdrs[2].hdr], true, false); + }, + function testDeleteMessages3() { + // normal delete from trash + var trashDB = gLocalTrashFolder.msgDatabase; + for (var i = 0; i < gMsgHdrs.length; i++) { + gMsgHdrs[i].hdr = trashDB.getMsgHdrForMessageID(gMsgHdrs[i].ID); + } + deleteMessages(gLocalTrashFolder, [gMsgHdrs[0].hdr], false, false); + }, + // shift delete from trash + function testDeleteMessages4() { + deleteMessages(gLocalTrashFolder, [gMsgHdrs[1].hdr], true, false); + }, + + // Renaming folders + function testRename1() { + renameFolder(gLocalFolder3, "folder4"); + }, + function testRename2() { + renameFolder(gLocalFolder2.getChildNamed("folder4"), "folder3"); + }, + function testRename3() { + renameFolder(gLocalFolder2, "folder4"); + }, + function testRename4() { + renameFolder( + localAccountUtils.inboxFolder.getChildNamed("folder4"), + "folder2" + ); + }, + + // Folder structure should still be + // Inbox + // -folder2 + // --folder3 + // -folder3 + // Trash + + // Deleting folders (currently only one folder delete is supported through the UI) + function deleteFolder1() { + deleteFolder(localAccountUtils.inboxFolder.getChildNamed("folder3"), null); + }, + // Folder structure should now be + // Inbox + // -folder2 + // --folder3 + // Trash + // -folder3 + function deleteFolder2() { + deleteFolder(localAccountUtils.inboxFolder.getChildNamed("folder2"), null); + }, + // Folder structure should now be + // Inbox + // Trash + // -folder2 + // --folder3 + // -folder3 + function deleteFolder3() { + deleteFolder(gLocalTrashFolder.getChildNamed("folder3"), null); + }, + // Folder structure should now be + // Inbox + // Trash + // -folder2 + // --folder3 + function deleteFolder4() { + // Let's take a moment to re-initialize stuff that got moved + gLocalFolder2 = gLocalTrashFolder.getChildNamed("folder2"); + gLocalFolder3 = gLocalFolder2.getChildNamed("folder3"); + deleteFolder(gLocalFolder2, gLocalFolder3); + }, + function compactInbox() { + if (localAccountUtils.inboxFolder.msgStore.supportsCompaction) { + compactFolder(localAccountUtils.inboxFolder); + } else { + doTest(++gTest); + } + }, +]; +// Folder structure should just be +// Inbox +// Trash + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + + // Add a listener. + MailServices.mfn.addListener(gMFListener, allTestedEvents); + + // Load up some messages so that we can copy them in later. + gMsgFile1 = do_get_file("../../../data/bugmail10"); + gMsgFile2 = do_get_file("../../../data/bugmail11"); + gMsgFile3 = do_get_file("../../../data/draft1"); + + // "Trash" folder + gRootFolder = localAccountUtils.incomingServer.rootMsgFolder; + gLocalTrashFolder = gRootFolder.getChildNamed("Trash"); + + // "Master" do_test_pending(), paired with a do_test_finished() at the end of all the operations. + do_test_pending(); + + // Do the test. + doTest(1); +} + +function doTest(test) { + if (test <= gTestArray.length) { + var testFn = gTestArray[test - 1]; + // Set a limit of 10 seconds; if the notifications haven't arrived by then there's a problem. + do_timeout(10000, function () { + if (gTest == test) { + do_throw( + "Notifications not received in 10000 ms for operation " + + testFn.name + + ", current status is " + + gCurrStatus + ); + } + }); + dump("=== Test: " + testFn.name + "\n"); + testFn(); + } else { + gHdrsReceived = null; + gMsgHdrs = null; + MailServices.mfn.removeListener(gMFListener); + do_test_finished(); // for the one in run_test() + } +} diff --git a/comm/mailnews/base/test/unit/test_nsIMsgTagService.js b/comm/mailnews/base/test/unit/test_nsIMsgTagService.js new file mode 100644 index 0000000000..52e36056ef --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsIMsgTagService.js @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests of nsIMsgTagService. + * + * Specifically tests changes implemented in bug 217034 + * Does not do comprehensive testing. + * + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + // These are both tags and keys. Note keys are forced to be lower case + const tag1 = "istag"; + const tag2 = "notistag"; + const tag3 = "istagnot"; + const tag4 = "istagtoo"; + + // add a tag + MailServices.tags.addTagForKey(tag1, tag1, null, null); + + // delete any existing tags + let tagArray = MailServices.tags.getAllTags(); + for (var i = 0; i < tagArray.length; i++) { + MailServices.tags.deleteKey(tagArray[i].key); + } + + // make sure added tag is now gone + Assert.ok(!MailServices.tags.isValidKey(tag1)); + + // add single tag, and check again + MailServices.tags.addTagForKey(tag1, tag1, null, null); + Assert.ok(MailServices.tags.isValidKey(tag1)); + Assert.ok(!MailServices.tags.isValidKey(tag4)); + + // add second tag and check + MailServices.tags.addTagForKey(tag4, tag4, null, null); + Assert.ok(MailServices.tags.isValidKey(tag1)); + Assert.ok(!MailServices.tags.isValidKey(tag2)); + Assert.ok(!MailServices.tags.isValidKey(tag3)); + Assert.ok(MailServices.tags.isValidKey(tag4)); + + // delete a tag and check + MailServices.tags.deleteKey(tag1); + Assert.ok(!MailServices.tags.isValidKey(tag1)); + Assert.ok(!MailServices.tags.isValidKey(tag2)); + Assert.ok(!MailServices.tags.isValidKey(tag3)); + Assert.ok(MailServices.tags.isValidKey(tag4)); + + // add many tags and check again + for (i = 0; i < 100; i++) { + MailServices.tags.addTagForKey(i, "lotsatags" + i, null, null); + } + Assert.ok(!MailServices.tags.isValidKey(tag1)); + Assert.ok(!MailServices.tags.isValidKey(tag2)); + Assert.ok(!MailServices.tags.isValidKey(tag3)); + Assert.ok(MailServices.tags.isValidKey(tag4)); + + for (i = 0; i < 100; i++) { + Assert.ok(MailServices.tags.isValidKey(i)); + // make sure it knows the difference betweens tags and keys + Assert.ok(!MailServices.tags.isValidKey("lotsatags" + i)); + // are we confused by key at start of tag? + Assert.ok(!MailServices.tags.isValidKey(i + "lotsatags")); + } + + // Test sort ordering for getAllTags() without ordinal. + for (let tag of MailServices.tags.getAllTags()) { + MailServices.tags.deleteKey(tag.key); + } + MailServices.tags.addTag("grapefruit", null, null); + MailServices.tags.addTag("orange", null, null); + MailServices.tags.addTag("lime", null, null); + MailServices.tags.addTag("lemon", null, null); + + // Should be sorted by tag name. + let tagNames = MailServices.tags.getAllTags().map(t => t.tag); + Assert.deepEqual( + tagNames, + ["grapefruit", "lemon", "lime", "orange"], + "Sort without ordinals" + ); + + // Test sort ordering for getAllTags() with (some) ordinals. + for (let tag of MailServices.tags.getAllTags()) { + MailServices.tags.deleteKey(tag.key); + } + MailServices.tags.addTag("grapefruit", null, "3"); + MailServices.tags.addTag("orange", null, "1"); + MailServices.tags.addTag("lime", null, null); + MailServices.tags.addTag("lemon", null, "2"); + + // Should be sorted by ordinal, then tag name. + tagNames = MailServices.tags.getAllTags().map(t => t.tag); + Assert.deepEqual( + tagNames, + ["orange", "lemon", "grapefruit", "lime"], + "Sort with ordinals" + ); +} + +/* +function printTags() { + for (let tag of MailServices.tags.getAllTags()) { + print(`# key [${tag.key}] tag [${tag.tag}] ordinal [${tag.ordinal}]`); + } +} +*/ diff --git a/comm/mailnews/base/test/unit/test_nsMailDirProvider.js b/comm/mailnews/base/test/unit/test_nsMailDirProvider.js new file mode 100644 index 0000000000..19b848f3fd --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMailDirProvider.js @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsMailDirProvider to check we get the right directories and + * files. + */ + +function run_test() { + const items = [ + { key: "MailD", value: "Mail" }, + { key: "IMapMD", value: "ImapMail" }, + { key: "NewsD", value: "News" }, + { key: "MLFCaF", value: "panacea.dat" }, // Legacy folder cache. + { key: "MFCaF", value: "folderCache.json" }, + ]; + + items.forEach(function (item) { + var dir = Services.dirsvc.get(item.key, Ci.nsIFile); + dump(do_get_profile().path + " " + dir.path + "\n"); + Assert.ok(do_get_profile().equals(dir.parent)); + + Assert.equal(dir.leafName, item.value); + }); +} diff --git a/comm/mailnews/base/test/unit/test_nsMsgDBView.js b/comm/mailnews/base/test/unit/test_nsMsgDBView.js new file mode 100644 index 0000000000..cb5527deab --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMsgDBView.js @@ -0,0 +1,1212 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Attempt to test nsMsgDBView and descendents. Right now this means we: + * - Ensure sorting and grouping sorta works, including using custom columns. + * + * Things we really should do: + * - Test that secondary sorting works, especially when the primary column is + * a custom column. + * + * You may also want to look into the test_viewWrapper_*.js tests as well. + */ + +var { MessageGenerator, MessageScenarioFactory, SyntheticMessageSet } = + ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm"); +const { TreeSelection } = ChromeUtils.importESModule( + "chrome://messenger/content/tree-selection.mjs" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); +var { dump_view_contents } = ChromeUtils.import( + "resource://testing-common/mozmill/ViewHelpers.jsm" +); + +// Items used to add messages to the folder +var gMessageGenerator = new MessageGenerator(); +var gScenarioFactory = new MessageScenarioFactory(gMessageGenerator); +var messageInjection = new MessageInjection({ mode: "local" }); + +var gTestFolder; +var gSiblingsMissingParentsSubject; +var gMessages; + +function setup_messages() { + // build up a diverse list of messages + let messages = []; + messages = messages.concat(gScenarioFactory.directReply(10)); + // the message generator uses a constanty incrementing counter, so we need to + // mix up the order of messages ourselves to ensure that the timestamp + // ordering is not already in order. (a poor test of sorting otherwise.) + messages = gScenarioFactory.directReply(6).concat(messages); + + messages = messages.concat(gScenarioFactory.fullPyramid(3, 3)); + let siblingMessages = gScenarioFactory.siblingsMissingParent(); + // cut off "Re: " part + gSiblingsMissingParentsSubject = siblingMessages[0].subject.slice(4); + dump("siblings subect = " + gSiblingsMissingParentsSubject + "\n"); + messages = messages.concat(siblingMessages); + messages = messages.concat(gScenarioFactory.missingIntermediary()); + // This next line was found to be faulty during linting, but fixing it breaks the test. + // messages.concat(gMessageGenerator.makeMessage({age: {days: 2, hours: 1}})); + + // build a hierarchy like this (the UID order corresponds to the date order) + // 1 + // 2 + // 4 + // 3 + let msg1 = gMessageGenerator.makeMessage(); + let msg2 = gMessageGenerator.makeMessage({ inReplyTo: msg1 }); + let msg3 = gMessageGenerator.makeMessage({ inReplyTo: msg1 }); + let msg4 = gMessageGenerator.makeMessage({ inReplyTo: msg2 }); + messages = messages.concat([msg1, msg2, msg3, msg4]); + + // test bug 600140, make a thread that Reply message has smaller MsgKey + let msgBiggerKey = gMessageGenerator.makeMessage(); + let msgSmallerKey = gMessageGenerator.makeMessage({ + inReplyTo: msgBiggerKey, + }); + messages = messages.concat([msgSmallerKey, msgBiggerKey]); + let msgSet = new SyntheticMessageSet(messages); + return msgSet; +} + +/** + * Sets gTestFolder with msgSet. Ensure that gTestFolder is clean for each test. + * + * @param {SyntheticMessageSet} msgSet + */ +async function set_gTestFolder(msgSet) { + gTestFolder = await messageInjection.makeEmptyFolder(); + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); +} + +/** + * Create a synthetic message by passing the provided aMessageArgs to + * the message generator, then add the resulting message to the given + * folder (or gTestFolder if no folder is provided). + */ +async function make_and_add_message(aMessageArgs) { + // create the message + let synMsg = gMessageGenerator.makeMessage(aMessageArgs); + let msgSet = new SyntheticMessageSet([synMsg]); + // this is synchronous for local stuff. + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); + + return [synMsg, msgSet]; +} + +function view_throw(why) { + dump_view_contents(); + do_throw(why); +} + +/** + * Throw if gDBView has any rows. + */ +function assert_view_empty() { + if (gTreeView.rowCount != 0) { + view_throw( + "Expected view to be empty, but it was not! (" + + gTreeView.rowCount + + " rows)" + ); + } +} + +/** + * Throw if gDBView does not have aCount rows. + */ +function assert_view_row_count(aCount) { + if (gTreeView.rowCount != aCount) { + view_throw( + "Expected view to have " + + aCount + + " rows, but it had " + + gTreeView.rowCount + + " rows!" + ); + } +} + +/** + * Throw if any of the arguments (as view indices) do not correspond to dummy + * rows in gDBView. + */ +function assert_view_index_is_dummy(...aArgs) { + for (let viewIndex of aArgs) { + let flags = gDBView.getFlagsAt(viewIndex); + if (!(flags & MSG_VIEW_FLAG_DUMMY)) { + view_throw("Expected index " + viewIndex + " to be a dummy!"); + } + } +} + +/** + * Throw if any of the arguments (as view indices) correspond to dummy rows in + * gDBView. + */ +function assert_view_index_is_not_dummy(...aArgs) { + for (let viewIndex of aArgs) { + let flags = gDBView.getFlagsAt(viewIndex); + if (flags & MSG_VIEW_FLAG_DUMMY) { + view_throw("Expected index " + viewIndex + " to not be a dummy!"); + } + } +} + +function assert_view_level_is(index, level) { + if (gDBView.getLevel(index) != level) { + view_throw( + "Expected index " + + index + + " to be level " + + level + + " not " + + gDBView.getLevel(index) + ); + } +} + +/** + * Given a message, assert that it is present at the given indices. + * + * Usage: + * assert_view_message_at_indices(synMsg, 0); + * assert_view_message_at_indices(synMsg, 0, 1); + * assert_view_message_at_indices(aMsg, 0, bMsg, 1); + */ +function assert_view_message_at_indices(...aArgs) { + let curHdr; + for (let thing of aArgs) { + if (typeof thing == "number") { + let hdrAt = gDBView.getMsgHdrAt(thing); + if (curHdr != hdrAt) { + view_throw( + "Expected hdr at " + + thing + + " to be " + + curHdr.messageKey + + ":" + + curHdr.mime2DecodedSubject.substr(0, 30) + + " not " + + hdrAt.messageKey + + ":" + + hdrAt.mime2DecodedSubject.substr(0, 30) + ); + } + } else { + // synthetic message, get the header... + curHdr = gTestFolder.msgDatabase.getMsgHdrForMessageID(thing.messageId); + } + } +} + +var authorFirstLetterCustomColumn = { + getCellText(row, col) { + let msgHdr = this.dbView.getMsgHdrAt(row); + return msgHdr.mime2DecodedAuthor.charAt(0).toUpperCase() || "?"; + }, + getSortStringForRow(msgHdr) { + // charAt(0) is a quote, charAt(1) is the first letter! + return msgHdr.mime2DecodedAuthor.charAt(1).toUpperCase() || "?"; + }, + isString() { + return true; + }, + + getCellProperties(row, col) { + return ""; + }, + getRowProperties(row) { + return ""; + }, + getImageSrc(row, col) { + return null; + }, + getSortLongForRow(hdr) { + return 0; + }, +}; + +var gDBView; +var gTreeView; + +var MSG_VIEW_FLAG_DUMMY = 0x20000000; + +var gFakeSelection = new TreeSelection(null); + +function setup_view(aViewType, aViewFlags, aTestFolder) { + let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + aViewType; + + if (aTestFolder == null) { + aTestFolder = gTestFolder; + } + + // always start out fully expanded + aViewFlags |= Ci.nsMsgViewFlagsType.kExpandAll; + + gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView); + gDBView.init(null, null, null); + var outCount = {}; + gDBView.open( + aViewType != "search" ? aTestFolder : null, + Ci.nsMsgViewSortType.byDate, + aViewType != "search" + ? Ci.nsMsgViewSortOrder.ascending + : Ci.nsMsgViewSortOrder.descending, + aViewFlags, + outCount + ); + // outCount is 0 if byCustom; view is built by addColumnHandler() + dump(" View Out Count: " + outCount.value + "\n"); + + // we need to cram messages into the search via nsIMsgSearchNotify interface + if ( + aViewType == "search" || + aViewType == "quicksearch" || + aViewType == "xfvf" + ) { + let searchNotify = gDBView.QueryInterface(Ci.nsIMsgSearchNotify); + searchNotify.onNewSearch(); + for (let msgHdr of aTestFolder.msgDatabase.enumerateMessages()) { + searchNotify.onSearchHit(msgHdr, msgHdr.folder); + } + searchNotify.onSearchDone(Cr.NS_OK); + } + + gDBView.addColumnHandler( + "authorFirstLetterCol", + authorFirstLetterCustomColumn + ); + // XXX this sets the custom column to use for sorting by the custom column. + // It has been argued (and is generally accepted) that this should not be + // so limited. + gDBView.curCustomColumn = "authorFirstLetterCol"; + + gTreeView = gDBView.QueryInterface(Ci.nsITreeView); + gTreeView.selection = gFakeSelection; + gFakeSelection.view = gTreeView; +} + +function setup_group_view(aSortType, aSortOrder, aTestFolder) { + let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=group"; + + if (aTestFolder == null) { + aTestFolder = gTestFolder; + } + + // grouped view uses these flags + let viewFlags = + Ci.nsMsgViewFlagsType.kGroupBySort | + Ci.nsMsgViewFlagsType.kExpandAll | + Ci.nsMsgViewFlagsType.kThreadedDisplay; + + gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView); + gDBView.init(null, null, null); + var outCount = {}; + gDBView.open(aTestFolder, aSortType, aSortOrder, viewFlags, outCount); + + gDBView.addColumnHandler( + "authorFirstLetterCol", + authorFirstLetterCustomColumn + ); + gDBView.curCustomColumn = "authorFirstLetterCol"; + + gTreeView = gDBView.QueryInterface(Ci.nsITreeView); + gFakeSelection.view = gTreeView; + gTreeView.selection = gFakeSelection; +} + +/** + * Comparison func for built-in types (including strings, so no subtraction.) + */ +function generalCmp(a, b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + return 0; +} + +/** + * Check that sort order and grouping logic (if applicable) are doing the right + * thing. + * + * In the case of groups (indicated by dummy headers), we want to ignore the + * dummies and 1) make sure all the values in the group have the same value, + * 2) verify that the headers meet our total ordering. + * In the case of threads, we want to ensure that each level of the hierarchy + * meets our ordering demands, recursing into children. Because the tree + * representation is rather quite horrible, the easiest thing for us is to + * track a per-level list of comparison values we have seen, nuking older + * values when changes in levels indicate closure of a level. (Namely, + * if we see a node at level N, then all levels >N are no longer valid.) + * + * @param {nsMsgViewType} aSortBy - The sort type. + * @param {nsMsgViewSortOrder} aDirection - The sort direction. + * @param {string|Function} aKeyOrValueGetter - A string naming the attribute on + * the message headerto retrieve, or if that is not sufficient a function that + * takes a message header and returns the sort value for it. + * @param {Function} [aGetGroupValue] - An optional function that takes a + * message header and returns the grouping value for the header. + * If omitted, it is assumed that the sort value is the grouping value. + */ +function ensure_view_ordering( + aSortBy, + aDirection, + aKeyOrValueGetter, + aGetGroupValue +) { + if (!gTreeView.rowCount) { + do_throw("There are no rows in my folder! I can't test anything!"); + } + dump( + " Ensuring sort order for " + + aSortBy + + " (Row count: " + + gTreeView.rowCount + + ")\n" + ); + dump(" cur view flags: " + gDBView.viewFlags + "\n"); + + // standard grouping doesn't re-group when you sort. so we need to actually + // re-initialize the view. + // but search mode is special and does the right thing because asuth didn't + // realize that it shouldn't do the right thing, so it can just change the + // sort. (of course, under the hood, it is actually creating a new view...) + if ( + gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort && + gDBView.viewType != Ci.nsMsgViewType.eShowSearch + ) { + // we must close to re-open (or we could just use a new view) + let msgFolder = gDBView.msgFolder; + gDBView.close(); + gDBView.open(msgFolder, aSortBy, aDirection, gDBView.viewFlags, {}); + } else { + gDBView.sort(aSortBy, aDirection); + } + + let comparisonValuesByLevel = []; + let expectedLevel0CmpResult = + aDirection == Ci.nsMsgViewSortOrder.ascending ? 1 : -1; + let comparator = generalCmp; + + let dummyCount = 0, + emptyDummyCount = 0; + + let valueGetter = + typeof aKeyOrValueGetter == "string" + ? function (msgHdr) { + return msgHdr[aKeyOrValueGetter]; + } + : aKeyOrValueGetter; + let groupValueGetter = aGetGroupValue || valueGetter; + + // don't do group testing until we see a dummy header (which we will see + // before we see any grouped headers, so it's fine to do this) + let inGroup = false; + // the current grouping value for the current group. this allows us to + // detect erroneous grouping of different group values together. + let curGroupValue = null; + // the set of group values observed before the current group. this allows + // us to detect improper grouping where there are multiple groups with the + // same grouping value. + let previouslySeenGroupValues = {}; + + for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) { + let msgHdr = gDBView.getMsgHdrAt(iViewIndex); + let msgViewFlags = gDBView.getFlagsAt(iViewIndex); + + // ignore dummy headers; testing grouping logic happens elsewhere + if (msgViewFlags & MSG_VIEW_FLAG_DUMMY) { + if (dummyCount && curGroupValue == null) { + emptyDummyCount++; + } + dummyCount++; + if (curGroupValue != null) { + previouslySeenGroupValues[curGroupValue] = true; + } + curGroupValue = null; + inGroup = true; + continue; + } + + // level is 0-based + let level = gTreeView.getLevel(iViewIndex); + // nuke existing comparison levels + if (level < comparisonValuesByLevel.length - 1) { + comparisonValuesByLevel.splice(level); + } + + // get the value for comparison + let curValue = valueGetter(msgHdr); + if (inGroup) { + let groupValue = groupValueGetter(msgHdr); + if (groupValue in previouslySeenGroupValues) { + do_throw(`Group value ${groupValue} observed in more than one group!`); + } + if (curGroupValue == null) { + curGroupValue = groupValue; + } else if (curGroupValue != groupValue) { + do_throw( + "Inconsistent grouping! " + groupValue + " != " + curGroupValue + ); + } + } + + // is this level new to our comparisons? then track it... + if (level >= comparisonValuesByLevel.length) { + // null-fill any gaps (due to, say, dummy nodes) + while (comparisonValuesByLevel.length <= level) { + comparisonValuesByLevel.push(null); + } + comparisonValuesByLevel.push(curValue); + } else { + // otherwise compare it + let prevValue = comparisonValuesByLevel[level - 1]; + let cmpResult = comparator(curValue, prevValue); + let expectedCmpResult = level > 0 ? 1 : expectedLevel0CmpResult; + if (cmpResult && cmpResult != expectedCmpResult) { + do_throw( + "Ordering failure on key " + + msgHdr.messageKey + + ". " + + curValue + + " should have been " + + (expectedCmpResult == 1 ? ">=" : "<=") + + " " + + prevValue + + " but was not." + ); + } + } + } + + if (inGroup && curGroupValue == null) { + emptyDummyCount++; + } + if (dummyCount) { + dump( + " saw " + + dummyCount + + " dummy headers (" + + emptyDummyCount + + " empty).\n" + ); + } +} + +/** + * Test sorting functionality. + */ +function test_sort_columns() { + ensure_view_ordering( + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.descending, + "date", + function getDateAgeBucket(msgHdr) { + // so, this is a cop-out, but we know that the date age bucket for our + // generated messages is always more than 2-weeks ago! + return 5; + } + ); + ensure_view_ordering( + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.ascending, + "date", + function getDateAgeBucket(msgHdr) { + // so, this is a cop-out, but we know that the date age bucket for our + // generated messages is always more than 2-weeks ago! + return 5; + } + ); + // (note, subject doesn't use dummy groups and so won't have grouping tested) + ensure_view_ordering( + Ci.nsMsgViewSortType.bySubject, + Ci.nsMsgViewSortOrder.ascending, + "mime2DecodedSubject" + ); + ensure_view_ordering( + Ci.nsMsgViewSortType.byAuthor, + Ci.nsMsgViewSortOrder.ascending, + "mime2DecodedAuthor" + ); + // Id + // Thread + // Priority + // Status + // Size + // Flagged + // Unread + ensure_view_ordering( + Ci.nsMsgViewSortType.byRecipient, + Ci.nsMsgViewSortOrder.ascending, + "mime2DecodedRecipients" + ); + // Location + // Tags + // JunkStatus + // Attachments + // Account + // Custom + ensure_view_ordering( + Ci.nsMsgViewSortType.byCustom, + Ci.nsMsgViewSortOrder.ascending, + function (msgHdr) { + return authorFirstLetterCustomColumn.getSortStringForRow(msgHdr); + } + ); + // Received +} + +function test_number_of_messages() { + // Bug 574799 + if (gDBView.numMsgsInView != gTestFolder.getTotalMessages(false)) { + do_throw( + "numMsgsInView is " + + gDBView.numMsgsInView + + " but should be " + + gTestFolder.getTotalMessages(false) + + "\n" + ); + } + // Bug 600140 + // Maybe elided so open it, now only consider the first one + if (gDBView.isContainer(0) && !gDBView.isContainerOpen(0)) { + gDBView.toggleOpenState(0); + } + let numMsgInTree = gTreeView.rowCount; + if (gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort) { + for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) { + let flags = gDBView.getFlagsAt(iViewIndex); + if (flags & MSG_VIEW_FLAG_DUMMY) { + numMsgInTree--; + } + } + } + if (gDBView.numMsgsInView != numMsgInTree) { + view_throw( + "message in tree is " + + numMsgInTree + + " but should be " + + gDBView.numMsgsInView + + "\n" + ); + } +} + +function test_selected_messages() { + gDBView.doCommand(Ci.nsMsgViewCommandType.expandAll); + + // Select one message + gTreeView.selection.select(1); + let selectedMessages = gDBView.getSelectedMsgHdrs(); + + if (selectedMessages.length != 1) { + do_throw( + "getSelectedMsgHdrs.length is " + + selectedMessages.length + + " but should be 1\n" + ); + } + + let firstSelectedMsg = gDBView.hdrForFirstSelectedMessage; + if (selectedMessages[0] != firstSelectedMsg) { + do_throw( + "getSelectedMsgHdrs[0] is " + + selectedMessages[0].messageKey + + " but should be " + + firstSelectedMsg.messageKey + + "\n" + ); + } + + // Select all messages + gTreeView.selection.selectAll(); + if (gDBView.numSelected != gTreeView.rowCount) { + do_throw( + "numSelected is " + + gDBView.numSelected + + " but should be " + + gTreeView.rowCount + + "\n" + ); + } + + selectedMessages = gDBView.getSelectedMsgHdrs(); + if (selectedMessages.length != gTestFolder.getTotalMessages(false)) { + do_throw( + "getSelectedMsgHdrs.length is " + + selectedMessages.length + + " but should be " + + gTestFolder.getTotalMessages(false) + + "\n" + ); + } + + for (let i = 0; i < selectedMessages.length; i++) { + let expectedHdr = gDBView.getMsgHdrAt(i); + if (!selectedMessages.includes(expectedHdr)) { + view_throw( + "Expected " + + expectedHdr.messageKey + + ":" + + expectedHdr.mime2DecodedSubject.substr(0, 30) + + " to be selected, but it wasn't\n" + ); + } + } + + gTreeView.selection.clearSelection(); +} + +function test_insert_remove_view_rows() { + // Test insertion/removal into m_keys. + let startCount = gTreeView.rowCount; + let index = 0; + let rows = 3; + let msgKey = rows * 1000; + let flags = 0; + let level = 0; + let folder = null; + let xfview = + gDBView.viewType == Ci.nsMsgViewType.eShowSearch || + gDBView.viewType == Ci.nsMsgViewType.eShowVirtualFolderResults; + if (xfview) { + folder = gDBView.getFolderForViewIndex(index); + } + + gDBView.insertTreeRows(index, rows, msgKey, flags, level, folder); + assert_view_row_count(startCount + rows); + let key = gDBView.getKeyAt(rows - 1); + if (key != msgKey) { + view_throw("msgKey is " + key + " but should be " + msgKey + "\n"); + } + gDBView.removeTreeRows(index, rows); + assert_view_row_count(startCount); + + // These should fail. + try { + gDBView.insertTreeRows(startCount + 10, rows, msgKey, flags, level, folder); + view_throw("expected exception not caught; inserting at illegal index \n"); + } catch (ex) {} + try { + gDBView.insertTreeRows(index, rows, msgKey, flags, level, folder); + gDBView.removeTreeRows(index, gTreeView.rowCount + 10); + view_throw("expected exception not caught; removing illegal rows \n"); + } catch (ex) { + gDBView.removeTreeRows(index, rows); + } + if (xfview) { + try { + gDBView.insertTreeRows(index, rows, msgKey, flags, level, null); + view_throw( + "expected exception not caught; folder required for xfvf view \n" + ); + } catch (ex) {} + } +} + +async function test_msg_added_to_search_view() { + // if the view is a non-grouped search view, test adding a header to + // the search results, and verify it gets put at top. + if (!(gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort)) { + gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending); + let [synMsg] = await make_and_add_message(); + let msgHdr = gTestFolder.msgDatabase.getMsgHdrForMessageID( + synMsg.messageId + ); + gDBView + .QueryInterface(Ci.nsIMsgSearchNotify) + .onSearchHit(msgHdr, msgHdr.folder); + assert_view_message_at_indices(synMsg, 0); + } +} + +function IsHdrChildOf(possibleParent, possibleChild) { + let parentHdrId = possibleParent.messageId; + let numRefs = possibleChild.numReferences; + for (let refIndex = 0; refIndex < numRefs; refIndex++) { + if (parentHdrId == possibleChild.getStringReference(refIndex)) { + return true; + } + } + return false; +} + +// This could be part of ensure_view_ordering() but I don't want to make that +// function any harder to read. +function test_threading_levels() { + if (!gTreeView.rowCount) { + do_throw("There are no rows in my folder! I can't test anything!"); + } + // only look at threaded, non-grouped views. + if ( + gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort || + !(gDBView.viewFlags & Ci.nsMsgViewFlagsType.kThreadedDisplay) + ) { + return; + } + + let prevLevel = 1; + let prevMsgHdr; + for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) { + let msgHdr = gDBView.getMsgHdrAt(iViewIndex); + let level = gTreeView.getLevel(iViewIndex); + if (level > prevLevel && msgHdr.subject != gSiblingsMissingParentsSubject) { + if (!IsHdrChildOf(prevMsgHdr, msgHdr)) { + view_throw("indented message not child of parent"); + } + } + prevLevel = level; + prevMsgHdr = msgHdr; + } +} + +function test_expand_collapse() { + let oldRowCount = gDBView.rowCount; + let thirdChild = gDBView.getMsgHdrAt(3); + gDBView.toggleOpenState(0); + if (gDBView.rowCount != oldRowCount - 9) { + view_throw("collapsing first item should have removed 9 items"); + } + + // test that expand/collapse works with killed sub-thread. + oldRowCount = gDBView.rowCount; + gTestFolder.msgDatabase.markHeaderKilled(thirdChild, true, null); + gDBView.toggleOpenState(0); + if (gDBView.rowCount != oldRowCount + 2) { + view_throw("expanding first item should have aded 2 items"); + } + gTestFolder.msgDatabase.markHeaderKilled(thirdChild, false, null); + oldRowCount = gDBView.rowCount; + gDBView.toggleOpenState(0); + if (gDBView.rowCount != oldRowCount - 2) { + view_throw("collapsing first item should have removed 2 items"); + } +} + +function test_qs_results() { + // This just tests that bug 505967 hasn't regressed. + if (gTreeView.getLevel(0) != 0) { + view_throw("first message should be at level 0"); + } + if (gTreeView.getLevel(1) != 1) { + view_throw("second message should be at level 1"); + } + if (gTreeView.getLevel(2) != 2) { + view_throw("third message should be at level 2"); + } + test_threading_levels(); +} + +async function test_group_sort_collapseAll_expandAll_threading() { + // - start with an empty folder + gTestFolder = await messageInjection.makeEmptyFolder(); + + // - create a normal unthreaded view + setup_view("threaded", 0); + + // - ensure it's empty + assert_view_empty(); + + // - add 3 messages: + // msg1: from A, custom column val A, to be starred + // msg2: from A, custom column val A + // msg3: from B, custom column val B + let [smsg1] = await make_and_add_message({ from: ["A", "A@a.invalid"] }); + await make_and_add_message({ from: ["A", "A@a.invalid"] }); + let [smsg3] = await make_and_add_message({ from: ["B", "B@b.invalid"] }); + + assert_view_row_count(3); + gDBView.getMsgHdrAt(0).markFlagged(true); + if (!gDBView.getMsgHdrAt(0).isFlagged) { + view_throw("Expected smsg1 to be flagged"); + } + + // - create grouped view; open folder in byFlagged AZ sort + setup_group_view( + Ci.nsMsgViewSortType.byFlagged, + Ci.nsMsgViewSortOrder.ascending, + gTestFolder + ); + // - make sure there are 5 rows; index 0 and 2 are dummy, 1 is flagged message, + // 3-4 are messages + assert_view_row_count(5); + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1); + assert_view_message_at_indices(smsg1, 1); + if (!gDBView.getMsgHdrAt(1).isFlagged) { + view_throw("Expected grouped smsg1 to be flagged"); + } + assert_view_index_is_dummy(2); + assert_view_index_is_not_dummy(3); + assert_view_index_is_not_dummy(4); + + // - collapse the grouped threads; there should be 2 dummy rows + gDBView.doCommand(Ci.nsMsgViewCommandType.collapseAll); + assert_view_row_count(2); + assert_view_index_is_dummy(0); + assert_view_index_is_dummy(1); + + // - expand the grouped threads; there should be 5 rows + gDBView.doCommand(Ci.nsMsgViewCommandType.expandAll); + assert_view_row_count(5); + assert_view_index_is_dummy(0); + assert_view_index_is_dummy(2); + + // - reverse sort; create grouped view; open folder in byFlagged ZA sort + setup_group_view( + Ci.nsMsgViewSortType.byFlagged, + Ci.nsMsgViewSortOrder.descending, + gTestFolder + ); + // - make sure there are 5 rows; index 0 and 3 are dummy, 1-2 are messages, + // 4 is flagged message + assert_view_row_count(5); + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1); + assert_view_index_is_not_dummy(2); + assert_view_index_is_dummy(3); + assert_view_index_is_not_dummy(4); + assert_view_message_at_indices(smsg1, 4); + if (!gDBView.getMsgHdrAt(4).isFlagged) { + view_throw("Expected reverse sorted grouped smsg1 to be flagged"); + } + + // - test grouped by custom column; the custCol is first letter of author + // - create grouped view; open folder in byCustom ZA sort + setup_group_view( + Ci.nsMsgViewSortType.byCustom, + Ci.nsMsgViewSortOrder.descending, + gTestFolder + ); + + // - make sure there are 5 rows; index 0 and 2 are dummy, 1 is B value message, + // 3-4 are messages with A value + assert_view_row_count(5); + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1); + assert_view_message_at_indices(smsg3, 1); + if ( + authorFirstLetterCustomColumn.getSortStringForRow(gDBView.getMsgHdrAt(1)) != + "B" + ) { + view_throw( + "Expected grouped by custom column, ZA sortOrder smsg3 value to be B" + ); + } + assert_view_index_is_dummy(2); + assert_view_index_is_not_dummy(3); + assert_view_index_is_not_dummy(4); + if ( + authorFirstLetterCustomColumn.getSortStringForRow(gDBView.getMsgHdrAt(4)) != + "A" + ) { + view_throw( + "Expected grouped by custom column, ZA sortOrder smsg2 value to be A" + ); + } +} + +async function test_group_dummies_under_mutation_by_date() { + // - start with an empty folder + gTestFolder = await messageInjection.makeEmptyFolder(); + + // - create the view + setup_view("group", Ci.nsMsgViewFlagsType.kGroupBySort); + gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.ascending); + + // - ensure it's empty + assert_view_empty(); + + // - add a message from this week + // (we want to make sure all the messages end up in the same bucket and that + // the current day changing as we run the test does not change buckets + // either. bucket 1 is same day, bucket 2 is yesterday, bucket 3 is last + // week, so 2 days ago or older is always last week, even if we roll over + // and it becomes 3 days ago.) + let [smsg, synSet] = await make_and_add_message({ + age: { days: 2, hours: 1 }, + }); + + // - make sure the message and a dummy appear + assert_view_row_count(2); + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1); + assert_view_message_at_indices(smsg, 0, 1); + + // we used to display total in tag column - make sure we don't do that. + if (gDBView.cellTextForColumn(0, "tags") != "") { + view_throw("tag column shouldn't display total count in group view"); + } + + // - move the messages to the trash + await messageInjection.trashMessages(synSet); + + // - make sure the message and dummy disappear + assert_view_empty(); + + // - add two messages from this week (same date bucket concerns) + let [newer, newerSet] = await make_and_add_message({ + age: { days: 2, hours: 1 }, + }); + let [older] = await make_and_add_message({ age: { days: 2, hours: 2 } }); + + // - sanity check addition + assert_view_row_count(3); // 2 messages + 1 dummy + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1, 2); + // the dummy should be based off the older guy + assert_view_message_at_indices(older, 0, 1); + assert_view_message_at_indices(newer, 2); + + // - delete the message right under the dummy + // (this will be the newer one) + await messageInjection.trashMessages(newerSet); + + // - ensure we still have the dummy and the right child node + assert_view_row_count(2); + assert_view_index_is_dummy(0); + assert_view_index_is_not_dummy(1); + // now the dummy should be based off the remaining older one + assert_view_message_at_indices(older, 0, 1); +} + +async function test_xfvf_threading() { + // - start with an empty folder + let save_gTestFolder = gTestFolder; + gTestFolder = await messageInjection.makeEmptyFolder(); + + let messages = []; + // Add messages such that ancestors arrive after their descendents in + // various interesting ways. + // build a hierarchy like this (the UID order corresponds to the date order) + // 3 + // 1 + // 4 + // 2 + // 5 + let msg3 = gMessageGenerator.makeMessage({ age: { days: 2, hours: 5 } }); + let msg1 = gMessageGenerator.makeMessage({ + age: { days: 2, hours: 4 }, + inReplyTo: msg3, + }); + let msg4 = gMessageGenerator.makeMessage({ + age: { days: 2, hours: 3 }, + inReplyTo: msg1, + }); + let msg2 = gMessageGenerator.makeMessage({ + age: { days: 2, hours: 1 }, + inReplyTo: msg4, + }); + let msg5 = gMessageGenerator.makeMessage({ + age: { days: 2, hours: 2 }, + inReplyTo: msg1, + }); + messages = messages.concat([msg1, msg2, msg3, msg4, msg5]); + + let msgSet = new SyntheticMessageSet(messages); + + gTestFolder = await messageInjection.makeEmptyFolder(); + + // - create the view + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); + setup_view("xfvf", Ci.nsMsgViewFlagsType.kThreadedDisplay); + assert_view_row_count(5); + gDBView.toggleOpenState(0); + gDBView.toggleOpenState(0); + + assert_view_message_at_indices(msg3, 0); + assert_view_message_at_indices(msg1, 1); + assert_view_message_at_indices(msg4, 2); + assert_view_message_at_indices(msg2, 3); + assert_view_message_at_indices(msg5, 4); + assert_view_level_is(0, 0); + assert_view_level_is(1, 1); + assert_view_level_is(2, 2); + assert_view_level_is(3, 3); + assert_view_level_is(4, 2); + gTestFolder = save_gTestFolder; +} + +/* + * Tests the sorting order of collapsed threads, not of messages within + * threads. Currently limited to testing the sort-threads-by-date case, + * sorting both by thread root and by newest message. + */ +async function test_thread_sorting() { + let save_gTestFolder = gTestFolder; + gTestFolder = await messageInjection.makeEmptyFolder(); + let messages = []; + // build a hierarchy like this (the UID order corresponds to the date order) + // 1 + // 4 + // 2 + // 5 + // 3 + let msg1 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 10 } }); + let msg2 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 9 } }); + let msg3 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 8 } }); + let msg4 = gMessageGenerator.makeMessage({ + age: { days: 1, hours: 7 }, + inReplyTo: msg1, + }); + let msg5 = gMessageGenerator.makeMessage({ + age: { days: 1, hours: 6 }, + inReplyTo: msg2, + }); + messages = messages.concat([msg1, msg2, msg3, msg4, msg5]); + + let msgSet = new SyntheticMessageSet(messages); + + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); + + // test the non-default pref state first, so the pref gets left with its + // default value at the end + Services.prefs.setBoolPref("mailnews.sort_threads_by_root", true); + gDBView.open( + gTestFolder, + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.ascending, + Ci.nsMsgViewFlagsType.kThreadedDisplay, + {} + ); + + assert_view_row_count(3); + assert_view_message_at_indices(msg1, 0); + assert_view_message_at_indices(msg2, 1); + assert_view_message_at_indices(msg3, 2); + + gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending); + assert_view_message_at_indices(msg3, 0); + assert_view_message_at_indices(msg2, 1); + assert_view_message_at_indices(msg1, 2); + + Services.prefs.clearUserPref("mailnews.sort_threads_by_root"); + gDBView.open( + gTestFolder, + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.ascending, + Ci.nsMsgViewFlagsType.kThreadedDisplay, + {} + ); + + assert_view_row_count(3); + assert_view_message_at_indices(msg3, 0); + assert_view_message_at_indices(msg1, 1); + assert_view_message_at_indices(msg2, 2); + + gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending); + assert_view_message_at_indices(msg2, 0); + assert_view_message_at_indices(msg1, 1); + assert_view_message_at_indices(msg3, 2); + + gDBView.close(); + gTestFolder = save_gTestFolder; +} + +const VIEW_TYPES = [ + ["threaded", Ci.nsMsgViewFlagsType.kThreadedDisplay], + ["quicksearch", Ci.nsMsgViewFlagsType.kThreadedDisplay], + ["search", Ci.nsMsgViewFlagsType.kThreadedDisplay], + ["search", Ci.nsMsgViewFlagsType.kGroupBySort], + ["xfvf", Ci.nsMsgViewFlagsType.kNone], + // group does unspeakable things to gTestFolder, so put it last. + ["group", Ci.nsMsgViewFlagsType.kGroupBySort], +]; + +/** + * These are tests which are for every test configuration. + */ +function tests_for_all_views() { + test_sort_columns(); + test_number_of_messages(); + test_selected_messages(); + test_insert_remove_view_rows(); +} + +add_setup(function () { + gMessages = setup_messages(); +}); + +add_task(async function test_threaded() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[0]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for threaded. + test_expand_collapse(); + await test_thread_sorting(); +}); + +add_task(async function test_quicksearch_threaded() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[1]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for quicksearch threaded. + test_qs_results(); +}); + +add_task(async function test_search_threaded() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[2]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for search threaded. + await test_msg_added_to_search_view(); +}); + +add_task(async function test_search_group_by_sort() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[3]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for search group by sort. + await test_msg_added_to_search_view(); +}); + +add_task(async function test_xfvf() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[4]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for xfvf. + await test_xfvf_threading(); +}); + +add_task(async function test_group() { + await set_gTestFolder(gMessages); + let [view_type, view_flag] = VIEW_TYPES[5]; + setup_view(view_type, view_flag); + + tests_for_all_views(); + + // Specific tests for group. + await test_group_sort_collapseAll_expandAll_threading; + await test_group_dummies_under_mutation_by_date; +}); + +add_task(function test_teardown() { + // Delete view reference to avoid a cycle leak. + gFakeSelection.view = null; +}); diff --git a/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js b/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js new file mode 100644 index 0000000000..796257a8a8 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test that nsMsgDBView properly reports the values of messages in the display. + */ + +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); + +var messageInjection = new MessageInjection({ mode: "local" }); + +// This is an array of the actual test data. Each test datum is an array of two +// elements: the first element is the argument into a simple message generator, +// and the second element is a map of column names to expected values when +// requesting the cell text for a given column name. +var tests = [ + [{ from: "John Doe <db@tinderbox.invalid>" }, { senderCol: "John Doe" }], + [{ from: '"Doe, John" <db@tinderbox.invalid>' }, { senderCol: "Doe, John" }], + // Multiple senders are indicated with 'et al.' suffix. + [ + { from: "John Doe <db@tinderbox.invalid>, Sally Ann <db@null.invalid>" }, + { senderCol: "John Doe et al." }, + ], + [ + { from: "=?UTF-8?Q?David_H=C3=A5s=C3=A4ther?= <db@null.invalid>" }, + { senderCol: "David Håsäther" }, + ], + [ + { from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= <db@null.invalid>" }, + { senderCol: "Håsäther, David" }, + ], + [ + { from: '"Håsäther, David" <db@null.invalid>' }, + { senderCol: "Håsäther, David" }, + ], + [ + { from: "David Håsäther <db@null.invalid>" }, + { senderCol: "David Håsäther" }, + ], + [ + { + from: "\xC2\xAB\xCE\xA0\xCE\x9F\xCE\x9B\xCE\x99\xCE\xA4\xCE\x97\xCE\xA3\xC2\xBB", + }, + { senderCol: "«ΠΟΛΙΤΗΣ»" }, + ], + [ + { + from: "John Doe \xF5 <db@null.invalid>", + clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-1" }, + }, + { senderCol: "John Doe õ" }, + ], + [ + { + from: "John Doe \xF5 <db@null.invalid>", + clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-2" }, + }, + { senderCol: "John Doe ő" }, + ], + [ + { + from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= <db@null.invalid>", + clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-2" }, + }, + { senderCol: "Håsäther, David" }, + ], +]; + +add_task(async function test_nsMsgDBView_headValues() { + // Add the messages to the folder + let msgGenerator = new MessageGenerator(); + let genMessages = tests.map(data => msgGenerator.makeMessage(data[0])); + let folder = await messageInjection.makeEmptyFolder(); + await messageInjection.addSetsToFolders( + [folder], + [new SyntheticMessageSet(genMessages)] + ); + + // Make the DB view + let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=threaded"; + let dbView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView); + dbView.init(null, null, null); + let outCount = {}; + dbView.open( + folder, + Ci.nsMsgViewSortType.byDate, + Ci.nsMsgViewSortOrder.ascending, + 0, + outCount + ); + + // Did we add all the messages properly? + let treeView = dbView.QueryInterface(Ci.nsITreeView); + Assert.equal(treeView.rowCount, tests.length); + + // For each test, make sure that the display is correct. + tests.forEach(function (data, i) { + info("Checking data for " + uneval(data)); + let expected = data[1]; + for (let column in expected) { + Assert.equal(dbView.cellTextForColumn(i, column), expected[column]); + } + }); +}); diff --git a/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js new file mode 100644 index 0000000000..61c1cf3fe9 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsMsgMailSession functions relating to alerts and their + * listeners. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +load("../../../resources/alertTestUtils.js"); + +var gDialogTitle = null; +var gText = null; + +function reset() { + gDialogTitle = null; + gText = null; +} + +/* exported alert */ +// Used in alertTestUtils. +function alert(aDialogTitle, aText) { + Assert.equal(gDialogTitle, null); + Assert.equal(gText, null); + + gDialogTitle = aDialogTitle; + gText = aText; +} + +var msgWindow = { + get promptDialog() { + return alertUtilsPrompts; + }, + + QueryInterface: ChromeUtils.generateQI(["nsIMsgWindow"]), +}; + +var msgUrl = { + _msgWindow: null, + + get msgWindow() { + return this._msgWindow; + }, + + QueryInterface: ChromeUtils.generateQI(["nsIMsgMailNewsUrl"]), +}; + +function alertListener() {} + +alertListener.prototype = { + mReturn: false, + mMessage: null, + mMsgWindow: null, + + reset() { + this.mMessage = null; + this.mMsgWindow = null; + }, + + onAlert(aMessage, aMsgWindow) { + Assert.equal(this.mMessage, null); + Assert.equal(this.mMsgWindow, null); + + this.mMessage = aMessage; + this.mMsgWindow = aMsgWindow; + + return this.mReturn; + }, + QueryInferface: ChromeUtils.generateQI([Ci.nsIMsgMailNewsUrl]), +}; + +function run_test() { + // Test - No listeners, check alert tries to alert the user. + + reset(); + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("test message", msgUrl); + + // The dialog title doesn't get set at the moment. + Assert.equal(gDialogTitle, null); + Assert.equal(gText, "test message"); + + // Test - No listeners and no msgWindow, check no alerts. + + reset(); + + msgUrl._msgWindow = null; + + MailServices.mailSession.alertUser("test no message", msgUrl); + + // The dialog title doesn't get set at the moment. + Assert.equal(gDialogTitle, null); + Assert.equal(gText, null); + + // Test - One listener, returning false (prompt should still happen). + + reset(); + + var listener1 = new alertListener(); + listener1.mReturn = false; + + MailServices.mailSession.addUserFeedbackListener(listener1); + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("message test", msgUrl); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, "message test"); + + Assert.equal(listener1.mMessage, "message test"); + Assert.notEqual(listener1.mMsgWindow, null); + + // Test - One listener, returning false, no msg window (prompt shouldn't + // happen). + + reset(); + listener1.reset(); + + MailServices.mailSession.alertUser("message test no prompt", null); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, null); + + Assert.equal(listener1.mMessage, "message test no prompt"); + Assert.equal(listener1.mMsgWindow, null); + + // Test - Two listeners, both returning false (prompt should happen). + + reset(); + listener1.reset(); + + var listener2 = new alertListener(); + listener2.mReturn = false; + + MailServices.mailSession.addUserFeedbackListener(listener2); + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("two listeners", msgUrl); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, "two listeners"); + + Assert.equal(listener1.mMessage, "two listeners"); + Assert.notEqual(listener1.mMsgWindow, null); + + Assert.equal(listener2.mMessage, "two listeners"); + Assert.notEqual(listener2.mMsgWindow, null); + + // Test - Two listeners, one returning true (prompt shouldn't happen). + + reset(); + listener1.reset(); + listener2.reset(); + + listener2.mReturn = true; + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("no prompt", msgUrl); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, null); + + Assert.equal(listener1.mMessage, "no prompt"); + Assert.notEqual(listener1.mMsgWindow, null); + + Assert.equal(listener2.mMessage, "no prompt"); + Assert.notEqual(listener2.mMsgWindow, null); + + // Test - Remove a listener. + + reset(); + listener1.reset(); + listener2.reset(); + + MailServices.mailSession.removeUserFeedbackListener(listener1); + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("remove listener", msgUrl); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, null); + + Assert.equal(listener1.mMessage, null); + Assert.equal(listener1.mMsgWindow, null); + + Assert.equal(listener2.mMessage, "remove listener"); + Assert.notEqual(listener2.mMsgWindow, null); + + // Test - Remove the other listener. + + reset(); + listener1.reset(); + listener2.reset(); + + MailServices.mailSession.removeUserFeedbackListener(listener2); + + msgUrl._msgWindow = msgWindow; + + MailServices.mailSession.alertUser("no listeners", msgUrl); + + Assert.equal(gDialogTitle, null); + Assert.equal(gText, "no listeners"); + + Assert.equal(listener1.mMessage, null); + Assert.equal(listener1.mMsgWindow, null); + + Assert.equal(listener2.mMessage, null); + Assert.equal(listener2.mMsgWindow, null); +} diff --git a/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js new file mode 100644 index 0000000000..e6fa785d79 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsMsgMailSession functions relating to listeners. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var numListenerFunctions = 8; + +// The MailSession also implements nsIFolderListener - used to relay +// notifications onward to all the registered listeners. +var gMailSessionNotifier = MailServices.mailSession.QueryInterface( + Ci.nsIFolderListener +); + +var gFLAll; +var gFLSingle = new Array(numListenerFunctions); + +function fL() {} + +fL.prototype = { + mReceived: 0, + mAutoRemoveItem: false, + + onFolderAdded(parentFolder, child) { + this.mReceived |= Ci.nsIFolderListener.added; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onMessageAdded(parentFolder, msg) { + this.mReceived |= Ci.nsIFolderListener.added; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderRemoved(parentFolder, child) { + this.mReceived |= Ci.nsIFolderListener.removed; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onMessageRemoved(parentFolder, msg) { + this.mReceived |= Ci.nsIFolderListener.removed; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderPropertyChanged(item, property, oldValue, newValue) { + this.mReceived |= Ci.nsIFolderListener.propertyChanged; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderIntPropertyChanged(item, property, oldValue, newValue) { + this.mReceived |= Ci.nsIFolderListener.intPropertyChanged; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderBoolPropertyChanged(item, property, oldValue, newValue) { + this.mReceived |= Ci.nsIFolderListener.boolPropertyChanged; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderUnicharPropertyChanged(item, property, oldValue, newValue) { + this.mReceived |= Ci.nsIFolderListener.unicharPropertyChanged; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderPropertyFlagChanged(item, property, oldValue, newValue) { + this.mReceived |= Ci.nsIFolderListener.propertyFlagChanged; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, + onFolderEvent(parentItem, item) { + this.mReceived |= Ci.nsIFolderListener.event; + if (this.mAutoRemoveItem) { + MailServices.mailSession.RemoveFolderListener(this); + } + }, +}; + +function NotifyMailSession() { + gMailSessionNotifier.onFolderAdded(null, null); + gMailSessionNotifier.onMessageAdded(null, null); + gMailSessionNotifier.onFolderRemoved(null, null); + gMailSessionNotifier.onMessageRemoved(null, null); + gMailSessionNotifier.onFolderPropertyChanged(null, null, null, null); + gMailSessionNotifier.onFolderIntPropertyChanged(null, null, null, null); + gMailSessionNotifier.onFolderBoolPropertyChanged(null, null, null, null); + gMailSessionNotifier.onFolderUnicharPropertyChanged(null, null, null, null); + gMailSessionNotifier.onFolderPropertyFlagChanged(null, null, null, null); + gMailSessionNotifier.onFolderEvent(null, null); +} + +function run_test() { + var i; + + Assert.ok(MailServices.mailSession != null); + + // Test - Add a listener + + gFLAll = new fL(); + + MailServices.mailSession.AddFolderListener(gFLAll, Ci.nsIFolderListener.all); + + for (i = 0; i < numListenerFunctions; ++i) { + gFLSingle[i] = new fL(); + MailServices.mailSession.AddFolderListener(gFLSingle[i], Math.pow(2, i)); + } + + // Test - Notify listener on all available items + + NotifyMailSession(); + + Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1); + gFLAll.mReceived = 0; + + for (i = 0; i < numListenerFunctions; ++i) { + Assert.equal(gFLSingle[i].mReceived, Math.pow(2, i)); + gFLSingle[i].mReceived = 0; + + // And prepare for test 3. + gFLSingle[i].mAutoRemoveItem = true; + } + + // Test - Remove Single Listeners as we go through the functions + + // Check the for loop above for changes to the single listeners. + + NotifyMailSession(); + + Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1); + gFLAll.mReceived = 0; + + for (i = 0; i < numListenerFunctions; ++i) { + Assert.equal(gFLSingle[i].mReceived, Math.pow(2, i)); + gFLSingle[i].mReceived = 0; + } + + // Test - Ensure the single listeners have been removed. + + NotifyMailSession(); + + Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1); + gFLAll.mReceived = 0; + + for (i = 0; i < numListenerFunctions; ++i) { + Assert.equal(gFLSingle[i].mReceived, 0); + } + + // Test - Remove main listener + + MailServices.mailSession.RemoveFolderListener(gFLAll); +} diff --git a/comm/mailnews/base/test/unit/test_nsMsgTraitService.js b/comm/mailnews/base/test/unit/test_nsMsgTraitService.js new file mode 100644 index 0000000000..f75b76ada6 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_nsMsgTraitService.js @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var ts = Cc["@mozilla.org/msg-trait-service;1"].getService( + Ci.nsIMsgTraitService +); + +// junk-related traits set by default +var kJunkId = "mailnews@mozilla.org#junk"; +var kGoodId = "mailnews@mozilla.org#good"; +var kGoodIndex = Ci.nsIJunkMailPlugin.GOOD_TRAIT; +var kJunkIndex = Ci.nsIJunkMailPlugin.JUNK_TRAIT; + +// a dummy set of traits +var proId = "TheProTrait"; +var proName = "ProName"; +var antiId = "TheAntiTrait"; + +function run_test() { + // Check lastIndex prior to adding, 3 - 1000 are reserved for mailnews + Assert.equal(ts.lastIndex, 1000); + + // basic junk as traits should be setup automatically + Assert.equal( + kGoodId, + Services.prefs.getCharPref("mailnews.traits.id." + kGoodIndex) + ); + Assert.equal( + kJunkId, + Services.prefs.getCharPref("mailnews.traits.id." + kJunkIndex) + ); + Assert.equal( + kGoodId, + Services.prefs.getCharPref("mailnews.traits.antiId." + kJunkIndex) + ); + Assert.ok( + Services.prefs.getBoolPref("mailnews.traits.enabled." + kJunkIndex) + ); + + // add the pro and anti test traits + Assert.ok(!ts.isRegistered(proId)); + var proIndex = ts.registerTrait(proId); + Assert.ok(ts.isRegistered(proId)); + Assert.equal(proIndex, 1001); + Assert.equal(proIndex, ts.getIndex(proId)); + Assert.equal(proId, ts.getId(proIndex)); + var antiIndex = ts.registerTrait(antiId); + Assert.equal(proIndex, 1001); + Assert.equal(antiIndex, 1002); + + // check setting and getting things through the service + ts.setName(proId, proName); + Assert.equal(proName, ts.getName(proId)); + Assert.ok(!ts.getEnabled(proId)); + ts.setEnabled(proId, true); + Assert.ok(ts.getEnabled(proId)); + ts.setAntiId(proId, antiId); + Assert.equal(antiId, ts.getAntiId(proId)); + let proArray = ts.getEnabledProIndices(); + let antiArray = ts.getEnabledAntiIndices(); + Assert.equal(proArray.length, 2); + Assert.equal(antiArray.length, 2); + Assert.equal(proArray[1], proIndex); + Assert.equal(antiArray[1], antiIndex); + + // check of aliases + // add three random aliases + ts.addAlias(1, 501); + ts.addAlias(1, 502); + ts.addAlias(1, 601); + let aliases = ts.getAliases(1); + Assert.equal(aliases[0], 501); + Assert.equal(aliases[1], 502); + Assert.equal(aliases[2], 601); + + // remove the middle one + ts.removeAlias(1, 502); + aliases = ts.getAliases(1); + Assert.equal(aliases.length, 2); + Assert.equal(aliases[0], 501); + Assert.equal(aliases[1], 601); + + // try to add an existing value + ts.addAlias(1, 501); + aliases = ts.getAliases(1); + Assert.equal(aliases.length, 2); + Assert.equal(aliases[0], 501); + Assert.equal(aliases[1], 601); + + // now let's make sure this got saved in preferences + Assert.equal( + proId, + Services.prefs.getCharPref("mailnews.traits.id." + proIndex) + ); + Assert.equal( + proName, + Services.prefs.getCharPref("mailnews.traits.name." + proIndex) + ); + Assert.ok(Services.prefs.getBoolPref("mailnews.traits.enabled." + proIndex)); + Assert.equal( + antiId, + Services.prefs.getCharPref("mailnews.traits.antiId." + proIndex) + ); + + // remove the pro trait + ts.unRegisterTrait(proId); + Assert.ok(!ts.isRegistered(proId)); + + // check that this is also removed from prefs. The get calls should fail + try { + Services.prefs.getCharPref("mailnews.traits.id." + proIndex); + Assert.ok(false); + } catch (e) {} + + try { + Services.prefs.getCharPref("mailnews.traits.name." + proIndex); + Assert.ok(false); + } catch (e) {} + + try { + Services.prefs.getBoolPref("mailnews.traits.enabled." + proIndex); + Assert.ok(false); + } catch (e) {} + + try { + Services.prefs.getCharPref("mailnews.traits.antiId." + proIndex); + Assert.ok(false); + } catch (e) {} +} diff --git a/comm/mailnews/base/test/unit/test_postPluginFilters.js b/comm/mailnews/base/test/unit/test_postPluginFilters.js new file mode 100644 index 0000000000..de75307886 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_postPluginFilters.js @@ -0,0 +1,223 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * tests post-plugin message filters as implemented in bug 198100 + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +// Globals + +var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService( + Ci.nsIMsgDBService +); + +// command functions for test data +var kTrain = 0; // train a file as a trait +var kClass = 1; // classify files with traits + +var gTest; // currently active test +var gMsgHdr; // current message header + +var kJunkFile = "../../../data/bugmail1"; +var kGoodFile = "../../../data/draft1"; + +var kPriorityLow = 3; +var kPriorityHigh = 5; +var gInboxListener; // database listener object + +var gTests = [ + { + // train two different messages + command: kTrain, + fileName: kGoodFile, + traitId: MailServices.junk.GOOD_TRAIT, + }, + { + command: kTrain, + fileName: kJunkFile, + traitId: MailServices.junk.JUNK_TRAIT, + }, + { + // test a filter that acts on GOOD messages + command: kClass, + fileName: kGoodFile, + test() { + Assert.equal(kPriorityHigh, gMsgHdr.priority); + }, + }, + { + // test a filter that acts on JUNK messages + command: kClass, + fileName: kJunkFile, + test() { + Assert.equal(kPriorityLow, gMsgHdr.priority); + }, + }, +]; + +// main test +function run_test() { + // Setup some incoming filters, setting junk priority low, and good high. + + // Can't use the fake server, must use the deferredTo local server! + let filterList = localAccountUtils.incomingServer.getFilterList(null); + + // junkIsLow filter + let filter = filterList.createFilter("junkIsLow"); + let searchTerm = filter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.JunkStatus; + let value = searchTerm.value; + value.attrib = Ci.nsMsgSearchAttrib.JunkStatus; + value.junkStatus = MailServices.junk.JUNK; + searchTerm.value = value; + searchTerm.op = Ci.nsMsgSearchOp.Is; + searchTerm.booleanAnd = false; + filter.appendTerm(searchTerm); + let action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.ChangePriority; + action.priority = kPriorityLow; + filter.appendAction(action); + filter.filterType = Ci.nsMsgFilterType.PostPlugin; + filter.enabled = true; + filterList.insertFilterAt(0, filter); + + // goodIsHigh filter + filter = filterList.createFilter("goodIsHigh"); + searchTerm = filter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.JunkStatus; + value = searchTerm.value; + value.attrib = Ci.nsMsgSearchAttrib.JunkStatus; + value.junkStatus = MailServices.junk.GOOD; + searchTerm.value = value; + searchTerm.op = Ci.nsMsgSearchOp.Is; + searchTerm.booleanAnd = false; + filter.appendTerm(searchTerm); + action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.ChangePriority; + action.priority = kPriorityHigh; + filter.appendAction(action); + filter.filterType = Ci.nsMsgFilterType.PostPlugin; + filter.enabled = true; + filterList.insertFilterAt(1, filter); + + // setup a db listener to grab the message headers. There's probably an + // easier way, but this works. + gInboxListener = new DBListener(); + gDbService.registerPendingListener( + localAccountUtils.inboxFolder, + gInboxListener + ); + + do_test_pending(); + + startCommand(); +} + +function endTest() { + // Cleanup + dump(" Exiting mail tests\n"); + if (gInboxListener) { + gDbService.unregisterPendingListener(gInboxListener); + } + + gPOP3Pump = null; + + do_test_finished(); // for the one in run_test() +} + +var classifyListener = { + // nsIMsgTraitClassificationListener implementation + onMessageTraitsClassified(aMsgURI, aTraits, aPercents) { + // print("Message URI is " + aMsgURI); + if (!aMsgURI) { + // Ignore end-of-batch signal. + return; + } + + startCommand(); + }, +}; + +/** @implements {nsIDBChangeListener} */ +function DBListener() {} + +DBListener.prototype = { + onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {}, + + onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {}, + + onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) { + gMsgHdr = aHdrChanged; + }, + + onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {}, + + onAnnouncerGoingAway(instigator) { + if (gInboxListener) { + try { + POP3Pump.inbox.msgDatabase.removeListener(gInboxListener); + } catch (e) { + dump("listener not found\n"); + } + } + }, + + onReadChanged(aInstigator) {}, + + onJunkScoreChanged(aInstigator) {}, + + onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {}, + onEvent(aDB, aEvent) {}, +}; + +// start the next test command +function startCommand() { + if (gTest && gTest.test) { + dump("doing test " + gTest.test.name + "\n"); + gTest.test(); + } + if (!gTests.length) { + // Do we have more commands? + // no, all done + endTest(); + return; + } + + gTest = gTests.shift(); + switch (gTest.command) { + case kTrain: + // train message + var proArray = []; + proArray.push(gTest.traitId); + + MailServices.junk.setMsgTraitClassification( + getSpec(gTest.fileName), // aMsgURI + [], // aOldTraits + proArray, // aNewTraits + classifyListener // aTraitListener + ); + // null, // [optional] in nsIMsgWindow aMsgWindow + // null, // [optional] in nsIJunkMailClassificationListener aJunkListener + break; + + case kClass: + // classify message + gPOP3Pump.files = [gTest.fileName]; + gPOP3Pump.onDone = function () { + do_timeout(100, startCommand); + }; + gPOP3Pump.run(); + break; + } +} + +function getSpec(aFileName) { + var file = do_get_file(aFileName); + var uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIURL); + uri = uri.mutate().setQuery("type=application/x-message-display").finalize(); + return uri.spec; +} diff --git a/comm/mailnews/base/test/unit/test_retention.js b/comm/mailnews/base/test/unit/test_retention.js new file mode 100644 index 0000000000..38038015b1 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_retention.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Simple tests for retention settings. In particular, we'd like to make + * sure that applying retention settings works with the new code that avoids + * opening db's to apply retention settings if the folder doesn't override + * the server defaults. + */ + +var { MessageGenerator, MessageScenarioFactory, SyntheticMessageSet } = + ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm"); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); + +var gMessageGenerator = new MessageGenerator(); +var gScenarioFactory = new MessageScenarioFactory(gMessageGenerator); +var messageInjection = new MessageInjection({ mode: "local" }); + +var gTestFolder; + +add_setup(async function () { + // Add 10 messages + let messages = []; + messages = messages.concat(gScenarioFactory.directReply(10)); + + let msgSet = new SyntheticMessageSet(messages); + + gTestFolder = await messageInjection.makeEmptyFolder(); + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); +}); + +add_task(function test_retention() { + let numMessages = 10; + gTestFolder.msgDatabase = null; + gTestFolder.applyRetentionSettings(); + const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService( + Ci.nsIMsgDBService + ); + // adding messages leaves some headers around as garbage - make sure + // those are cleaned up so the db will get closed. + Cu.forceGC(); + Cu.forceCC(); + Assert.equal(gDbService.cachedDBForFolder(gTestFolder), null); + // no retention settings, so we should have the same number of messages. + Assert.equal(numMessages, gTestFolder.msgDatabase.dBFolderInfo.numMessages); + let serverSettings = gTestFolder.server.retentionSettings; + serverSettings.retainByPreference = + Ci.nsIMsgRetentionSettings.nsMsgRetainByNumHeaders; + serverSettings.numHeadersToKeep = 9; + gTestFolder.server.retentionSettings = serverSettings; + gTestFolder.applyRetentionSettings(); + // no retention settings, so we should have the same number of messages. + Assert.equal(9, gTestFolder.msgDatabase.dBFolderInfo.numMessages); + let folderSettings = gTestFolder.retentionSettings; + folderSettings.retainByPreference = + Ci.nsIMsgRetentionSettings.nsMsgRetainByNumHeaders; + folderSettings.numHeadersToKeep = 8; + folderSettings.useServerDefaults = false; + gTestFolder.retentionSettings = folderSettings; + gTestFolder.applyRetentionSettings(); + // no retention settings, so we should have the same number of messages. + Assert.equal(8, gTestFolder.msgDatabase.dBFolderInfo.numMessages); +}); diff --git a/comm/mailnews/base/test/unit/test_saveAs.js b/comm/mailnews/base/test/unit/test_saveAs.js new file mode 100644 index 0000000000..39263d3854 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_saveAs.js @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Invokes nsMessenger::saveAs with the bypass of the File Picker to check if + * the saved file as .eml .html or .txt contains certain strings (header, body, ...). + * + * See `checkedContent` for the compared strings. + */ + +var { MessageGenerator } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +var { ImapMessage } = ChromeUtils.import( + "resource://testing-common/mailnews/Imapd.jsm" +); +var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import( + "resource://testing-common/mailnews/IMAPpump.jsm" +); +var { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +var { FileTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/FileTestUtils.sys.mjs" +); + +var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger); + +setupIMAPPump(); + +registerCleanupFunction(function () { + teardownIMAPPump(); +}); + +/** + * Creates a SyntheticMessage and prepares it for loading it + * into a fake IMAP inbox. + * + * @returns {object[]} + * [0] is an {ImapMessage} + * [1] is an {SyntheticMessage} + */ +async function createMessage() { + let gMessageGenerator = new MessageGenerator(); + let synthMessage = gMessageGenerator.makeMessage(); + + let msgURI = Services.io.newURI( + "data:text/plain;base64," + btoa(synthMessage.toMessageString()) + ); + let imapInbox = IMAPPump.daemon.getMailbox("INBOX"); + let ImapMessageFromSynthMsg = new ImapMessage( + msgURI.spec, + imapInbox.uidnext++, + [] + ); + return [ImapMessageFromSynthMsg, synthMessage]; +} + +/** + * Adds a fake msg to a fake IMAP. + * + * @param {ImapMessage} fooMessage + */ +async function addImapMessage(fooMessage) { + IMAPPump.mailbox.addMessage(fooMessage); + let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener); + await promiseUrlListener.promise; +} + +/** + * Saves the file and waits until its able to load. + * nsMessenger doesn't have an event for the loaded File. + * + * @param {string} fileEnding + * @returns {string} + */ +async function saveAndLoad(fileEnding) { + // getTempFile guarantees that the file doesn't exist. + let tmpFile = FileTestUtils.getTempFile(`someprefix${fileEnding}`); + Assert.ok( + tmpFile.path.endsWith(fileEnding), + "Sanity check if the file ending is intact" + ); + + // Get the ImapMessage. + let hdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox); + let uri = IMAPPump.inbox.getUriForMsg(hdr); + + // nsMessenger::saveAs + messenger.saveAs(uri, true, null, tmpFile.path, true); + info(`File saved at ${tmpFile.path}`); + + await TestUtils.waitForCondition( + () => IOUtils.exists(tmpFile.path), + "wait for nsMessenger::saveAs file exists" + ); + let fileContent = await IOUtils.readUTF8(tmpFile.path); + return fileContent; +} + +/** + * All of these strings must appear in the saved file. + * + * @param {SyntheticMessage} synthMessage + * This message is the original message. + * @returns {object} + */ +function checkedContent(synthMessage) { + return { + // Skip dateCheck because of Formatting and Timezone. + // date: synthMessage.date.toString(), + fromName: synthMessage.from[0], + fromEmail: synthMessage.from[1], + subject: synthMessage.subject, + toName: synthMessage.toName, + toAddress: synthMessage.toAddress, + body: synthMessage.bodyPart.body, + }; +} + +async function emlTest(synthMessage) { + let loadedFileContent = await saveAndLoad(".eml"); + let messageParts = checkedContent(synthMessage); + for (const msgPart in messageParts) { + Assert.stringContains( + loadedFileContent, + messageParts[msgPart], + `nsMessenger::saveAs with .eml should contain ${msgPart}` + ); + } +} + +async function htmlTest(synthMessage) { + let loadedFileContent = await saveAndLoad(".html"); + let messageParts = checkedContent(synthMessage); + for (const msgPart in messageParts) { + Assert.stringContains( + loadedFileContent, + messageParts[msgPart], + `nsMessenger::saveAs with .html should contain ${msgPart}` + ); + } +} + +async function txtTest(synthMessage) { + let loadedFileContent = await saveAndLoad(".txt"); + let messageParts = checkedContent(synthMessage); + for (const msgPart in messageParts) { + Assert.stringContains( + loadedFileContent, + messageParts[msgPart], + `nsMessenger::saveAs with .txt should contain ${msgPart}` + ); + } +} + +add_task(async function test_saveAs() { + let [fakedImapMessage, synthMessage] = await createMessage(); + await addImapMessage(fakedImapMessage); + await emlTest(synthMessage); + await txtTest(synthMessage); + await htmlTest(synthMessage); +}); diff --git a/comm/mailnews/base/test/unit/test_testsuite_base64.js b/comm/mailnews/base/test/unit/test_testsuite_base64.js new file mode 100644 index 0000000000..a6faae3640 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_testsuite_base64.js @@ -0,0 +1,22 @@ +/** + * Tests functions atob() and btoa() in mailnews/test/resources/MailTestUtils.jsm . + * + * Note: + * btoa() = base64 encode + * atob() = base64 decode + * (i.e. "binary" = plain, and "ascii" = encoded) + */ + +function run_test() { + var plain = "testtesttest"; + var encoded = "dGVzdHRlc3R0ZXN0"; + + // correct encoding according to spec + Assert.equal(btoa(plain), encoded); // encode + Assert.equal(atob(encoded), plain); // decode + + // roundtrip works + Assert.equal(atob(btoa(plain)), plain); + Assert.equal(btoa(atob(encoded)), encoded); + return true; +} diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js new file mode 100644 index 0000000000..02181ae20b --- /dev/null +++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js @@ -0,0 +1,58 @@ +/** + * Tests functions in mailnews/test/fakeserver/Auth.jsm + * which are responsible for the authentication in the + * fakeserver. + * + * Do NOT essentially re-code the auth schemes here, + * just check roundtrips, against static values etc.. + */ + +var { AuthPLAIN, AuthCRAM } = ChromeUtils.import( + "resource://testing-common/mailnews/Auth.jsm" +); + +var kUsername = "fred1"; +var kPassword = "wilma2"; + +function run_test() { + authPLAIN(); + authCRAMMD5(); + return true; +} + +/** + * Test AUTH PLAIN + */ +function authPLAIN() { + // roundtrip works + var line = AuthPLAIN.encodeLine(kUsername, kPassword); + var req = AuthPLAIN.decodeLine(line); + Assert.equal(req.username, kUsername); + Assert.equal(req.password, kPassword); + + // correct encoding + Assert.equal(line, "AGZyZWQxAHdpbG1hMg=="); +} + +/** + * Test AUTH CRAM-MD5 + */ +function authCRAMMD5() { + // AuthCRAM.createChallenge() creates a different challenge each time + var hardcodedChallenge = btoa("<123@fake.invalid>"); + var hardcodedResponse = + "ZnJlZDEgOTA5YjgwMmM3NTI5NTJlYzI2NjgyMTNmYTdjNWU0ZjQ="; + + // correct encoding + var req = AuthCRAM.decodeLine(hardcodedResponse); + Assert.equal(req.username, kUsername); + var expectedDigest = AuthCRAM.encodeCRAMMD5(hardcodedChallenge, kPassword); + Assert.equal(req.digest, expectedDigest); + + var challenge = AuthCRAM.createChallenge("fake.invalid"); + challenge = atob(challenge); // decode. function currently returns it already encoded + var challengeSplit = challenge.split("@"); + Assert.equal(challengeSplit.length, 2); + Assert.equal(challengeSplit[1], "fake.invalid>"); + Assert.equal(challengeSplit[0][0], "<"); +} diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js new file mode 100644 index 0000000000..a9eb9ae2d7 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test that Imapd.jsm fakeserver correctly emulates GMail server +// That means X-GM-EXT-1 capability and GMail flavor XLIST +// per https://developers.google.com/google-apps/gmail/imap_extensions + +// IMAP pump +var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import( + "resource://testing-common/mailnews/IMAPpump.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +setupIMAPPump("GMail"); +// create our own handler so that we can call imapd functions directly +var handler; + +add_setup(function () { + Services.prefs.setBoolPref( + "mail.server.server1.autosync_offline_stores", + false + ); +}); + +// mbox mailboxes cannot contain both child mailboxes and messages, so this will +// be one test case. +add_setup(async function () { + IMAPPump.mailbox.specialUseFlag = "\\Inbox"; + IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\Noselect"] }); + IMAPPump.daemon.createMailbox("[Gmail]/All Mail", { + specialUseFlag: "\\AllMail", + }); + IMAPPump.daemon.createMailbox("[Gmail]/Drafts", { + specialUseFlag: "\\Drafts", + }); + IMAPPump.daemon.createMailbox("[Gmail]/Sent", { specialUseFlag: "\\Sent" }); + IMAPPump.daemon.createMailbox("[Gmail]/Spam", { specialUseFlag: "\\Spam" }); + IMAPPump.daemon.createMailbox("[Gmail]/Starred", { + specialUseFlag: "\\Starred", + }); + IMAPPump.daemon.createMailbox("[Gmail]/Trash", { specialUseFlag: "\\Trash" }); + IMAPPump.daemon.createMailbox("test", {}); + + handler = IMAPPump.server._handlerCreator(IMAPPump.daemon); + let response = handler.onError("1", "LOGIN user password"); + Assert.ok(response.includes("OK")); + // wait for imap pump to do its thing or else we get memory leaks + let listener = new PromiseTestUtils.PromiseUrlListener(); + IMAPPump.inbox.updateFolderWithListener(null, listener); + await listener.promise; +}); + +// test that 'XLIST "" "*"' returns the proper responses +add_task(function testXlist() { + let response = handler.onError("2", 'XLIST "" "*"'); + + Assert.ok(response.includes('* LIST (\\HasNoChildren \\Inbox) "/" "INBOX"')); + Assert.ok( + response.includes('* LIST (\\Noselect \\HasChildren) "/" "[Gmail]"') + ); + Assert.ok( + response.includes( + '* LIST (\\HasNoChildren \\AllMail) "/" "[Gmail]/All Mail"' + ) + ); + Assert.ok( + response.includes('* LIST (\\HasNoChildren \\Drafts) "/" "[Gmail]/Drafts"') + ); + Assert.ok( + response.includes('* LIST (\\HasNoChildren \\Sent) "/" "[Gmail]/Sent"') + ); + Assert.ok( + response.includes('* LIST (\\HasNoChildren \\Spam) "/" "[Gmail]/Spam"') + ); + Assert.ok( + response.includes( + '* LIST (\\HasNoChildren \\Starred) "/" "[Gmail]/Starred"' + ) + ); + Assert.ok( + response.includes('* LIST (\\HasNoChildren \\Trash) "/" "[Gmail]/Trash"') + ); + Assert.ok(response.includes('* LIST (\\HasNoChildren) "/" "test"')); +}); + +// Cleanup at end +add_task(function endTest() { + teardownIMAPPump(); +}); diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js new file mode 100644 index 0000000000..89214b2b51 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js @@ -0,0 +1,150 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test that Imapd.jsm fakeserver correctly implements LIST-EXTENDED imap +// extension (RFC 5258 - http://tools.ietf.org/html/rfc5258) + +// IMAP pump +var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import( + "resource://testing-common/mailnews/IMAPpump.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +// Globals + +// Dovecot is one of the servers that supports LIST-EXTENDED +setupIMAPPump("Dovecot"); +// create our own handler so that we can call imapd functions directly +var handler; + +add_setup(function () { + Services.prefs.setBoolPref( + "mail.server.server1.autosync_offline_stores", + false + ); +}); + +// mbox mailboxes cannot contain both child mailboxes and messages, so this will +// be one test case. +add_setup(async function () { + IMAPPump.mailbox.flags = ["\\Marked", "\\NoInferiors"]; + IMAPPump.mailbox.subscribed = true; + IMAPPump.daemon.createMailbox("Fruit", {}); + IMAPPump.daemon.createMailbox("Fruit/Apple", {}); + IMAPPump.daemon.createMailbox("Fruit/Banana", { subscribed: true }); + IMAPPump.daemon.createMailbox("Fruit/Peach", { + nonExistent: true, + subscribed: true, + }); + IMAPPump.daemon.createMailbox("Tofu", {}); + IMAPPump.daemon.createMailbox("Vegetable", { subscribed: true }); + IMAPPump.daemon.createMailbox("Vegetable/Broccoli", { subscribed: true }); + IMAPPump.daemon.createMailbox("Vegetable/Corn", {}); + + handler = IMAPPump.server._handlerCreator(IMAPPump.daemon); + let response = handler.onError("1", "LOGIN user password"); + Assert.ok(response.includes("OK")); + // wait for imap pump to do it's thing or else we get memory leaks + let listener = new PromiseTestUtils.PromiseUrlListener(); + IMAPPump.inbox.updateFolderWithListener(null, listener); + await listener.promise; +}); + +// test that 'LIST "" "*"' returns the proper responses (standard LIST usage) +add_task(function testList() { + let response = handler.onError("2", 'LIST "" "*"'); + + Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"')); + Assert.ok(response.includes('* LIST () "/" "Fruit"')); + Assert.ok(response.includes('* LIST () "/" "Fruit/Apple"')); + Assert.ok(response.includes('* LIST () "/" "Fruit/Banana"')); + Assert.ok(response.includes('* LIST () "/" "Tofu"')); + Assert.ok(response.includes('* LIST () "/" "Vegetable"')); + Assert.ok(response.includes('* LIST () "/" "Vegetable/Broccoli"')); + Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"')); + Assert.ok(!response.includes("Peach")); +}); + +// test that 'LIST (SUBSCRIBED) "" "*"' returns the proper responses +add_task(function testListSelectSubscribed() { + let response = handler.onError("3", 'LIST (SUBSCRIBED) "" "*"'); + + Assert.ok( + response.includes( + '* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"' + ) + ); + Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Fruit/Banana"')); + Assert.ok( + response.includes('* LIST (\\Subscribed \\NonExistent) "/" "Fruit/Peach"') + ); + Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Vegetable"')); + Assert.ok( + response.includes('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"') + ); + Assert.ok(!response.includes('"Fruit"')); + Assert.ok(!response.includes("Apple")); + Assert.ok(!response.includes("Tofu")); + Assert.ok(!response.includes("Corn")); +}); + +// test that 'LIST "" "%" RETURN (CHILDEREN)' returns the proper responses +add_task(function testListReturnChilderen() { + let response = handler.onError("4", 'LIST "" "%" RETURN (CHILDREN)'); + + Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"')); + Assert.ok(response.includes('* LIST (\\HasChildren) "/" "Fruit"')); + Assert.ok(response.includes('* LIST (\\HasNoChildren) "/" "Tofu"')); + Assert.ok(response.includes('* LIST (\\HasChildren) "/" "Vegetable"')); + Assert.ok(!response.includes("Apple")); + Assert.ok(!response.includes("Banana")); + Assert.ok(!response.includes("Peach")); + Assert.ok(!response.includes("Broccoli")); + Assert.ok(!response.includes("Corn")); +}); + +// test that 'LIST "" "*" RETURN (SUBSCRIBED)' returns the proper responses +add_task(function testListReturnSubscribed() { + let response = handler.onError("5", 'LIST "" "*" RETURN (SUBSCRIBED)'); + + Assert.ok( + response.includes( + '* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"' + ) + ); + Assert.ok(response.includes('* LIST () "/" "Fruit"')); + Assert.ok(response.includes('* LIST () "/" "Fruit/Apple"')); + Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Fruit/Banana"')); + Assert.ok(response.includes('* LIST () "/" "Tofu"')); + Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Vegetable"')); + Assert.ok( + response.includes('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"') + ); + Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"')); + Assert.ok(!response.includes("Peach")); +}); + +// test that 'LIST "" ("INBOX" "Tofu" "Vegetable/%")' returns the proper responses +add_task(function testListSelectMultiple() { + let response = handler._dispatchCommand("LIST", [ + "", + '("INBOX" "Tofu" "Vegetable/%")', + ]); + + Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"')); + Assert.ok(response.includes('* LIST () "/" "Tofu"')); + Assert.ok(response.includes('* LIST () "/" "Vegetable/Broccoli"')); + Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"')); + Assert.ok(!response.includes('"Vegetable"')); + Assert.ok(!response.includes("Fruit")); + Assert.ok(!response.includes("Peach")); +}); + +// Cleanup at end +add_task(function endTest() { + handler = null; + teardownIMAPPump(); +}); diff --git a/comm/mailnews/base/test/unit/test_viewSortByAddresses.js b/comm/mailnews/base/test/unit/test_viewSortByAddresses.js new file mode 100644 index 0000000000..7235723bfd --- /dev/null +++ b/comm/mailnews/base/test/unit/test_viewSortByAddresses.js @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Attempt to test nsMsgDBView's handling of sorting by sender/recipients + * when using a display name from the address book. + */ + +/* import-globals-from ../../../test/resources/abSetup.js */ +load("../../../resources/abSetup.js"); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { dump_view_contents } = ChromeUtils.import( + "resource://testing-common/mozmill/ViewHelpers.jsm" +); + +var gMessageGenerator = new MessageGenerator(); +var messageInjection = new MessageInjection({ mode: "local" }); + +Services.prefs.setBoolPref("mail.showCondensedAddresses", true); + +var gTestFolder; + +// Setup the display name to be opposite of alphabetic order of e-mail address. +var cards = [ + { email: "aaa@b.invalid", displayName: "4" }, + { email: "ccc@d.invalid", displayName: "3" }, + { email: "eee@f.invalid", displayName: "2" }, + { email: "ggg@h.invalid", displayName: "1" }, +]; + +add_setup(async function () { + // Ensure all the directories are initialised. + MailServices.ab.directories; + + let ab = MailServices.ab.getDirectory(kPABData.URI); + + function createAndAddCard(element) { + var card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance( + Ci.nsIAbCard + ); + + card.primaryEmail = element.email; + card.displayName = element.displayName; + + ab.addCard(card); + } + + // Add address to addressbook so we can set display name and verify that + // the view uses the display name for display and sorting. + cards.forEach(createAndAddCard); + + // build up a couple message with addresses in the ab. + let messages = []; + messages = messages.concat( + gMessageGenerator.makeMessage({ + from: ["aaa", "aaa@b.invalid"], + to: [["ccc", "ccc@d.invalid"]], + }) + ); + messages = messages.concat( + gMessageGenerator.makeMessage({ + from: ["eee", "eee@f.invalid"], + to: [["ggg", "ggg@h.invalid"]], + }) + ); + + let msgSet = new SyntheticMessageSet(messages); + gTestFolder = await messageInjection.makeEmptyFolder(); + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); +}); + +add_task(function test_view_sort_by_addresses() { + // - create the view + setup_view("threaded", Ci.nsMsgViewFlagsType.kNone); + // Check that sorting by sender uses the display name + gDBView.sort(Ci.nsMsgViewSortType.byAuthor, Ci.nsMsgViewSortOrder.ascending); + let sender1 = gDBView.cellTextForColumn(0, "senderCol"); + let sender2 = gDBView.cellTextForColumn(1, "senderCol"); + + if (sender1 != 2) { + view_throw("expected sender 1 to be 2"); + } + if (sender2 != 4) { + view_throw("expected sender 2 to be 4"); + } + + gDBView.sort( + Ci.nsMsgViewSortType.byRecipient, + Ci.nsMsgViewSortOrder.ascending + ); + let recip1 = gDBView.cellTextForColumn(0, "recipientCol"); + let recip2 = gDBView.cellTextForColumn(1, "recipientCol"); + + if (recip1 != 1) { + view_throw("expected recip 1 to be 1"); + } + if (recip2 != 3) { + view_throw("expected recip 2 to be 3"); + } +}); + +function view_throw(why) { + dump_view_contents(); + do_throw(why); +} +var gDBView; +var gTreeView; + +function setup_view(aViewType, aViewFlags, aTestFolder) { + let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + aViewType; + + if (aTestFolder == null) { + aTestFolder = gTestFolder; + } + + // always start out fully expanded + aViewFlags |= Ci.nsMsgViewFlagsType.kExpandAll; + + gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView); + gDBView.init(null, null, null); + var outCount = {}; + gDBView.open( + aViewType != "search" ? aTestFolder : null, + Ci.nsMsgViewSortType.byDate, + aViewType != "search" + ? Ci.nsMsgViewSortOrder.ascending + : Ci.nsMsgViewSortOrder.descending, + aViewFlags, + outCount + ); + dump(" View Out Count: " + outCount.value + "\n"); + + gTreeView = gDBView.QueryInterface(Ci.nsITreeView); +} diff --git a/comm/mailnews/base/test/unit/test_virtualFolders1.js b/comm/mailnews/base/test/unit/test_virtualFolders1.js new file mode 100644 index 0000000000..5fe32de237 --- /dev/null +++ b/comm/mailnews/base/test/unit/test_virtualFolders1.js @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests that subfolders added to searched folders are also searched. + */ + +const { VirtualFolderHelper } = ChromeUtils.import( + "resource:///modules/VirtualFolderWrapper.jsm" +); + +add_task(function () { + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.accounts[0]; + let rootFolder = account.incomingServer.rootFolder; + + // Create some real folders to test. + rootFolder.createSubfolder("test A", null); + let testFolderA = rootFolder.getChildNamed("test A"); + rootFolder.createSubfolder("test B", null); + let testFolderB = rootFolder.getChildNamed("test B"); + rootFolder.createSubfolder("test C", null); + let testFolderC = rootFolder.getChildNamed("test C"); + + // Create a virtual folder with no search folders. + let wrappedFolderZ = VirtualFolderHelper.createNewVirtualFolder( + "virtual Z", + rootFolder, + [], + "ANY", + false + ); + Assert.equal( + wrappedFolderZ.virtualFolder, + rootFolder.getChildNamed("virtual Z") + ); + Assert.equal(wrappedFolderZ.searchFolderURIs, ""); + Assert.deepEqual(wrappedFolderZ.searchFolders, []); + + // Create a virtual folder with one search folder. + let wrappedFolderY = VirtualFolderHelper.createNewVirtualFolder( + "virtual Y", + rootFolder, + [testFolderA], + "ANY", + false + ); + Assert.equal( + wrappedFolderY.virtualFolder, + rootFolder.getChildNamed("virtual Y") + ); + Assert.equal(wrappedFolderY.searchFolderURIs, testFolderA.URI); + Assert.deepEqual(wrappedFolderY.searchFolders, [testFolderA]); + + // Create a virtual folder with two search folders. + let wrappedFolderX = VirtualFolderHelper.createNewVirtualFolder( + "virtual X", + rootFolder, + [testFolderB, testFolderC], + "ANY", + false + ); + Assert.equal( + wrappedFolderX.virtualFolder, + rootFolder.getChildNamed("virtual X") + ); + Assert.equal( + wrappedFolderX.searchFolderURIs, + `${testFolderB.URI}|${testFolderC.URI}` + ); + Assert.deepEqual(wrappedFolderX.searchFolders, [testFolderB, testFolderC]); + + // Add a subfolder to real folder B. Check it is added to virtual folder X. + testFolderB.createSubfolder("test BB", null); + let testFolderBB = testFolderB.getChildNamed("test BB"); + Assert.equal( + wrappedFolderZ.searchFolderURIs, + "", + "virtual folder Z should not change" + ); + Assert.deepEqual( + wrappedFolderZ.searchFolders, + [], + "virtual folder Z should not change" + ); + Assert.equal( + wrappedFolderY.searchFolderURIs, + testFolderA.URI, + "virtual folder Y should not change" + ); + Assert.deepEqual( + wrappedFolderY.searchFolders, + [testFolderA], + "virtual folder Y should not change" + ); + Assert.equal( + wrappedFolderX.searchFolderURIs, + `${testFolderB.URI}|${testFolderC.URI}|${testFolderBB.URI}` + ); + Assert.deepEqual(wrappedFolderX.searchFolders, [ + testFolderB, + testFolderBB, + testFolderC, + ]); + + // Add a subfolder to real folder BB. Check it is added to virtual folder X. + testFolderBB.createSubfolder("test BBB", null); + let testFolderBBB = testFolderBB.getChildNamed("test BBB"); + Assert.equal( + wrappedFolderZ.searchFolderURIs, + "", + "virtual folder Z should not change" + ); + Assert.deepEqual( + wrappedFolderZ.searchFolders, + [], + "virtual folder Z should not change" + ); + Assert.equal( + wrappedFolderY.searchFolderURIs, + testFolderA.URI, + "virtual folder Y should not change" + ); + Assert.deepEqual( + wrappedFolderY.searchFolders, + [testFolderA], + "virtual folder Y should not change" + ); + Assert.equal( + wrappedFolderX.searchFolderURIs, + `${testFolderB.URI}|${testFolderC.URI}|${testFolderBB.URI}|${testFolderBBB.URI}` + ); + Assert.deepEqual(wrappedFolderX.searchFolders, [ + testFolderB, + testFolderBB, + testFolderBBB, + testFolderC, + ]); + + // Remove BB from virtual folder X. + wrappedFolderX.searchFolders = [testFolderB, testFolderBBB, testFolderC]; + Assert.equal( + wrappedFolderZ.searchFolderURIs, + "", + "virtual folder Z should not change" + ); + Assert.deepEqual( + wrappedFolderZ.searchFolders, + [], + "virtual folder Z should not change" + ); + Assert.equal( + wrappedFolderY.searchFolderURIs, + testFolderA.URI, + "virtual folder Y should not change" + ); + Assert.deepEqual( + wrappedFolderY.searchFolders, + [testFolderA], + "virtual folder Y should not change" + ); + Assert.equal( + wrappedFolderX.searchFolderURIs, + `${testFolderB.URI}|${testFolderBBB.URI}|${testFolderC.URI}` + ); + Assert.deepEqual(wrappedFolderX.searchFolders, [ + testFolderB, + testFolderBBB, + testFolderC, + ]); + + // Add a second subfolder to the removed folder. Check it is not added to + // virtual folder X, because the parent folder is not in X. + testFolderBB.createSubfolder("test BBB two", null); + Assert.equal( + wrappedFolderZ.searchFolderURIs, + "", + "virtual folder Z should not change" + ); + Assert.deepEqual( + wrappedFolderZ.searchFolders, + [], + "virtual folder Z should not change" + ); + Assert.equal( + wrappedFolderY.searchFolderURIs, + testFolderA.URI, + "virtual folder Y should not change" + ); + Assert.deepEqual( + wrappedFolderY.searchFolders, + [testFolderA], + "virtual folder Y should not change" + ); + Assert.equal( + wrappedFolderX.searchFolderURIs, + `${testFolderB.URI}|${testFolderBBB.URI}|${testFolderC.URI}` + ); + Assert.deepEqual(wrappedFolderX.searchFolders, [ + testFolderB, + testFolderBBB, + testFolderC, + ]); +}); diff --git a/comm/mailnews/base/test/unit/test_virtualFolders2.js b/comm/mailnews/base/test/unit/test_virtualFolders2.js new file mode 100644 index 0000000000..07c75db97f --- /dev/null +++ b/comm/mailnews/base/test/unit/test_virtualFolders2.js @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests problems emptying a Trash folder that is searched by a virtual folder. + */ + +const { VirtualFolderHelper } = ChromeUtils.import( + "resource:///modules/VirtualFolderWrapper.jsm" +); + +add_task(function () { + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.accounts[0]; + let rootFolder = account.incomingServer.rootFolder; + + // Create a real folders inside the trash folder. + let trashFolder = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash); + trashFolder.createSubfolder("deleted", null); + let deletedFolder = trashFolder.getChildNamed("deleted"); + + // Create a virtual folder that searches the trash folder. + let wrappedVirtualFolder = VirtualFolderHelper.createNewVirtualFolder( + "virtual", + rootFolder, + [trashFolder, deletedFolder], + "ANY", + false + ); + let virtualFolder = wrappedVirtualFolder.virtualFolder; + Assert.equal(virtualFolder, rootFolder.getChildNamed("virtual")); + Assert.equal( + wrappedVirtualFolder.searchFolderURIs, + `${trashFolder.URI}|${deletedFolder.URI}` + ); + Assert.deepEqual(wrappedVirtualFolder.searchFolders, [ + trashFolder, + deletedFolder, + ]); + + // Create a smart virtual folder that searches the trash folder. This is the + // same as before except we'll set the searchFolderFlag property, as we do + // for the Unified Folders section of the UI. + let wrappedSmartFolder = VirtualFolderHelper.createNewVirtualFolder( + "smart", + rootFolder, + [trashFolder, deletedFolder], + "ANY", + false + ); + let smartFolder = wrappedSmartFolder.virtualFolder; + smartFolder.msgDatabase.dBFolderInfo.setUint32Property( + "searchFolderFlag", + Ci.nsMsgFolderFlags.Trash + ); + Assert.equal(smartFolder, rootFolder.getChildNamed("smart")); + Assert.equal( + wrappedSmartFolder.searchFolderURIs, + `${trashFolder.URI}|${deletedFolder.URI}` + ); + Assert.deepEqual(wrappedSmartFolder.searchFolders, [ + trashFolder, + deletedFolder, + ]); + + // Empty the trash. The normal virtual folder should disappear, but the + // smart folder shouldn't. + trashFolder.emptyTrash(null); + Assert.equal( + virtualFolder.parent, + null, + "virtual folder should be removed with last search folder" + ); + Assert.equal( + smartFolder.parent, + rootFolder, + "smart virtual folder should NOT be removed with last search folder" + ); + Assert.equal( + wrappedSmartFolder.searchFolderURIs, + trashFolder.URI, + "smart virtual folder should still search the trash folder (only)" + ); + Assert.deepEqual( + wrappedSmartFolder.searchFolders, + [trashFolder], + "smart virtual folder should still search the trash folder (only)" + ); +}); diff --git a/comm/mailnews/base/test/unit/test_virtualFolders3.js b/comm/mailnews/base/test/unit/test_virtualFolders3.js new file mode 100644 index 0000000000..e3d1b9c73c --- /dev/null +++ b/comm/mailnews/base/test/unit/test_virtualFolders3.js @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests that subfolders added to searched folders are also searched. + */ + +const { VirtualFolderHelper } = ChromeUtils.import( + "resource:///modules/VirtualFolderWrapper.jsm" +); + +let rootFolder; + +add_setup(function () { + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.accounts[0]; + rootFolder = account.incomingServer.rootFolder; + rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + + // Listen to folder events for debugging purposes. + + MailServices.mailSession.AddFolderListener( + { + QueryInterface: ChromeUtils.generateQI(["nsIFolderListener"]), + onFolderAdded(parentFolder, childFolder) { + console.log( + `onFolderAdded: ${ + childFolder?.URI + } (flags: ${childFolder.flags.toString(16)}) added to ${ + parentFolder?.URI + }` + ); + }, + onMessageAdded(parentFolder, msg) {}, + onFolderRemoved(parentFolder, childFolder) { + console.log( + `onFolderRemoved: ${ + childFolder?.URI + } (flags: ${childFolder.flags.toString(16)}) removed from ${ + parentFolder?.URI + }` + ); + }, + onMessageRemoved(parentFolder, msg) {}, + onFolderPropertyChanged(folder, property, oldValue, newValue) {}, + onFolderIntPropertyChanged(folder, property, oldValue, newValue) { + if (property == "FolderFlag") { + console.log( + `onFolderIntPropertyChanged: ${ + folder.URI + } changed flags from ${oldValue.toString( + 16 + )} to ${newValue.toString(16)}` + ); + } + }, + onFolderBoolPropertyChanged(folder, property, oldValue, newValue) {}, + onFolderUnicharPropertyChanged(folder, property, oldValue, newValue) {}, + onFolderPropertyFlagChanged(folder, property, oldFlag, newFlag) {}, + onFolderEvent(folder, event) {}, + }, + Ci.nsIFolderListener.all + ); +}); + +// Test each of the folder types. +add_task(function testInbox() { + subtest("Inbox"); +}); +add_task(function testDrafts() { + subtest("Drafts"); +}); +add_task(function testTemplates() { + subtest("Templates"); +}); +add_task(function testSentMail() { + subtest("SentMail"); +}); +add_task(function testArchive() { + subtest("Archive"); +}); +add_task(function testJunk() { + subtest("Junk"); +}); +add_task(function testTrash() { + subtest("Trash"); +}); + +function subtest(flag) { + // Create a virtual folder. This is very similar to the code in about3Pane.js. + + let virtualFolder = rootFolder.createLocalSubfolder(`virtual${flag}`); + virtualFolder.flags |= + Ci.nsMsgFolderFlags.Virtual | Ci.nsMsgFolderFlags[flag]; + + let msgDatabase = virtualFolder.msgDatabase; + let folderInfo = msgDatabase.dBFolderInfo; + + folderInfo.setCharProperty("searchStr", "ALL"); + folderInfo.setUint32Property("searchFolderFlag", Ci.nsMsgFolderFlags[flag]); + folderInfo.setBooleanProperty("searchOnline", true); + msgDatabase.summaryValid = true; + msgDatabase.close(true); + + function checkVirtualFolder(searchFolders, message) { + let wrappedVirtualFolder = + VirtualFolderHelper.wrapVirtualFolder(virtualFolder); + Assert.deepEqual( + wrappedVirtualFolder.searchFolderURIs.split("|").filter(Boolean).sort(), + searchFolders.map(f => f.URI).sort(), + message + ); + Assert.deepEqual( + wrappedVirtualFolder.searchFolders, + searchFolders, + message + ); + } + + rootFolder.notifyFolderAdded(virtualFolder); + checkVirtualFolder([], "new virtual folder should have no search folders"); + + // Create a disconnected folder with some descendants, add the flag and then + // add it to the parent. The folder and descendants should all be added to + // the virtual folder. + + let parent = MailServices.folderLookup.getOrCreateFolderForURL( + `${rootFolder.URI}/parent${flag}` + ); + parent.setFlag(Ci.nsMsgFolderFlags[flag]); + let child = MailServices.folderLookup.getOrCreateFolderForURL( + `${rootFolder.URI}/parent${flag}/child` + ); + parent.addSubfolder(child.name); + let grandchild = MailServices.folderLookup.getOrCreateFolderForURL( + `${rootFolder.URI}/parent${flag}/child/grandchild` + ); + child.addSubfolder(grandchild.name); + + rootFolder.addSubfolder(parent.name); + rootFolder.notifyFolderAdded(parent); + parent.notifyFolderAdded(child); + child.notifyFolderAdded(grandchild); + + checkVirtualFolder( + [parent, child, grandchild], + "added folder and descendants should be added to the virtual folder" + ); + + // Create a subfolder of a real folder with some descendants, then set the + // flag. The folder and descendants should all be added to the virtual folder. + + let more = rootFolder.createLocalSubfolder(`more${flag}`); + more.QueryInterface(Ci.nsIMsgLocalMailFolder); + let evenMore = more.createLocalSubfolder("even more"); + evenMore.QueryInterface(Ci.nsIMsgLocalMailFolder); + let yetMore = evenMore.createLocalSubfolder("yet more"); + more.setFlag(Ci.nsMsgFolderFlags[flag]); + + checkVirtualFolder( + [more, evenMore, yetMore, parent, child, grandchild], + "folder with changed flag and descendants should be added to the virtual folder" + ); + + // Test what happens if a folder of one type is not added to a parent in the + // virtual folder of another type. This should really only matter for inboxes + // containing other types of folders, which happens in some configurations. + // Other combinations shouldn't really exist, but let's test them anyway. + + if (!["SentMail", "Archive"].includes(flag)) { + for (let otherFlag of [ + "Inbox", + "Drafts", + "Templates", + "SentMail", + "Archive", + "Junk", + "Trash", + ]) { + if (otherFlag == flag) { + continue; + } + let otherFlagChild = MailServices.folderLookup.getOrCreateFolderForURL( + `${rootFolder.URI}/parent${flag}/other${otherFlag}Child` + ); + otherFlagChild.setFlag(Ci.nsMsgFolderFlags[otherFlag]); + parent.addSubfolder(otherFlagChild.name); + parent.notifyFolderAdded(otherFlagChild); + + if (flag == "Trash") { + checkVirtualFolder( + [more, evenMore, yetMore, parent, child, grandchild, otherFlagChild], + `folder with ${otherFlag} flag should be added to the virtual ${flag} folder` + ); + } else { + checkVirtualFolder( + [more, evenMore, yetMore, parent, child, grandchild], + `folder with ${otherFlag} flag should not be added to the virtual ${flag} folder` + ); + } + + parent.propagateDelete(otherFlagChild, false); + } + } + + // Now reverse the additions. + + rootFolder.propagateDelete(parent, false); + checkVirtualFolder( + [more, evenMore, yetMore], + "deleted folder and descendants should be removed from the virtual folder" + ); + + if (flag != "Inbox") { + // Clearing the inbox flag from a folder that has it throws an assertion. + more.clearFlag(Ci.nsMsgFolderFlags[flag]); + checkVirtualFolder( + [], + "folder with changed flag and descendants should be removed from the virtual folder" + ); + + Assert.equal( + virtualFolder.parent, + rootFolder, + "smart virtual folder should NOT be removed with last search folder" + ); + } +} diff --git a/comm/mailnews/base/test/unit/xpcshell-imap.ini b/comm/mailnews/base/test/unit/xpcshell-imap.ini new file mode 100644 index 0000000000..2b343b30b7 --- /dev/null +++ b/comm/mailnews/base/test/unit/xpcshell-imap.ini @@ -0,0 +1,8 @@ +[DEFAULT] +head = head_mailbase.js +tail = +prefs = + mailnews.imap.jsmodule=true +dupe-manifest = + +[test_incomingServer.js] diff --git a/comm/mailnews/base/test/unit/xpcshell.ini b/comm/mailnews/base/test/unit/xpcshell.ini new file mode 100644 index 0000000000..0658d7d1b3 --- /dev/null +++ b/comm/mailnews/base/test/unit/xpcshell.ini @@ -0,0 +1,77 @@ +[DEFAULT] +head = head_mailbase.js +tail = +dupe-manifest = +prefs = + mail.biff.use_new_count_in_badge=false +support-files = nodelist_test.xml data/* + +[test_accountMgr.js] +[test_accountMgr2.js] +[test_accountMgrCustomTypes.js] +[test_accountMgrMovedLocalFolders.js] +[test_accountMgrRemoveDefault.js] +[test_accountMigration.js] +[test_acctRepair.js] +[test_bccInDatabase.js] +[test_bug428427.js] +[test_bug434810.js] +[test_bug471682.js] +[test_bug514945.js] +[test_closedDB.js] +[test_compactColumnSave.js] +[test_compactFailure.js] +[test_converterDeferredAccount.js] +[test_copyChaining.js] +[test_copyToInvalidDB.js] +[test_detachToFile.js] +[test_emptyTrash.js] +[test_fix_deferred_accounts.js] +[test_folderCompact.js] +[test_folderCompact2.js] +[test_folderLookupService.js] +[test_folderStringProperties.js] +[test_formatFileSize.js] +[test_getMsgTextFromStream.js] +[test_headerFoldingInDatabase.js] +[test_hostnameUtils.js] +[test_identity.js] +[test_imapPump.js] +[test_incomingServer.js] +[test_inheritedFolderProperties.js] +[test_junkingWhenDisabled.js] +[test_loadVirtualFolders.js] +[test_mailServices.js] +[test_mailstoreConverter.js] +[test_mimemaltdetach.js] +[test_MsgIncomingServer.js] +[test_MsgKeySet.js] +[test_newMailNotification.js] +# Not yet working for non-Mac OS +skip-if = os != 'mac' +[test_nsIFolderListener.js] +[test_nsIMsgContentPolicy.js] +skip-if = true # See bug 1446587. +[test_nsIMsgFolder.js] +[test_nsIMsgFolderCache.js] +[test_nsIMsgFolderListener.js] +[test_nsIMsgFolderListenerLocal.js] +[test_nsIMsgTagService.js] +[test_nsMailDirProvider.js] +[test_nsMsgDBView.js] +[test_nsMsgDBView_headerValues.js] +[test_nsMsgMailSession_Alerts.js] +skip-if = true # See bug 1418063. +[test_nsMsgMailSession_Listeners.js] +[test_nsMsgTraitService.js] +[test_postPluginFilters.js] +[test_retention.js] +[test_saveAs.js] +[test_testsuite_base64.js] +[test_testsuite_fakeserver_imapd_gmail.js] +[test_testsuite_fakeserver_imapd_list-extended.js] +[test_testsuite_fakeserverAuth.js] +[test_viewSortByAddresses.js] +[test_virtualFolders1.js] +[test_virtualFolders2.js] +[test_virtualFolders3.js] |