Как стать автором
Обновить

Попробуй пройди за 4 часа тестовое задание в канадскую компанию

Время на прочтение18 мин
Количество просмотров36K

Поделюсь своим свежим опытом непрохождения интервью, может кому поможет пройти отбор и получить работу. Называть компанию я не буду из-за возможного риска преследования с их стороны, но если вы пообщаетесь с ними, то легко узнаете по деталям из статьи.

Я сам опытный интервьюер и собеседовал сотни кандидатов. А так же достаточно часто собеседовался в поисках работы. Открою карты сразу и опишу свое отношение к тестовым заданиям и процессам интервью: обычно я стараюсь их избегать при прочих равных в выборе. Я не против оплачиваемых тестовых заданий, либо совместных кодинг сессий за компьютером с интервьювером. Считаю что задачка должна быть достаточно компактной и решение занимать не больше пары часов - с одной стороны так можно проверить навыки кодинга кандидата, а с другой стороны не забирать драгоценное время жизни на сферический код в вакууме. Ну и это должен быть диалог двух специалистов, а не выдача "на дом" того с чем кандидату могут помогать поисковая система и сокурсники из Мумбая.

Это только присказка-сказка впереди

Пару слов про процессы интервью. Работодатель может реально оценивать себя и то чего хочет достичь. Если нужен реальный инженер, который знает, умеет и имеет подтвержденный опыт в индустрии и компания не из FAANG: Google, Microsoft, Facebook, Amazon итп., то можете не придумывать свои мануалы для кандидатов про процессам длиной в жизнь с алгоритмическими задачами, поведенческими интервью, комитетами и прочей бюрократией. Сюда же и ожидания что инженер будет час расхваливать историю компании и цели при вопросе "Почему вы выбрали нашу компанию? Что вы знаете о ней?". IMHO человеку с опытом в индустрии разработки ПО достаточно просто не быть чудаком с буквы м, чтобы пройти отбор на софтскилы. Мы не говорим сейчас про менеджеров в управлении разработкой, там царят другие правила отбора.

С чем я столкнулся с октября при поиске работы, что рынок перевернулся с ног на голову - сейчас это не рынок кандидата на иностранные вакансии и работодатели снова диктуют условия на отбор. Опять же все это пока и ждем когда вернется дефицит, все циклично. Почему я не хочу работать на какого-либо ИП или российскую корпорацию оставим вне контекта этой публикации. Хотя... Последние 9 лет я работал в международных компаниях и привык к комфорту для психики, баланса личного времени и финансов в сравнении с тем что предлагал российский рынок. Также я проработал в другой стране с декабря 2021 в одном из крупнейших туристических агрегаторов на азиатском рынке, бигдатя бигдату. Только сейчас начал признаваться себе что где-то я погорячился с уходом с последнего места работы. Пусть сфера туризма еще полностью не отошла от пандемии, со всеми вытекающими для работника.

Из нескольких вариантов компаний что нашел месяц назад, я решил податься туда где процесс был более простой и реалистичный. Первый этап - созвон с HR специалистом и разговоры о жизни и мотивации. Второй этап - hackerrank тест на 45мин. Третьий - поведенческое интервью с техническим руководителем. Четвертый этап это тестовое задание на Java, по словам HR это где-то на 4-6 часов но строго с секундомером не следят за временем выполнения. После всех этих шагов может последовать оффер или еще один этап с CTO.

Типичные процессы интервью в последние 16 лет у меня были - общение с HR, техническое интервью иногда совмещенное с интервью с менеджером или техдиром и оффер следом иногда с проверкой службы безопасности до этого! Почувствуйте разницу.

Альтернативой в тот момент еще было общение с HR на вакансию Fivetran - я сразу же попрощался когда узнал какой у них длинный процесс с решением алгоритмических задач в виде онлайн кодинга. Да и "усталость" и тон разговора со мной кадрового специалиста насторожила с первых же минут видео созвона. Потом посмотрел что у них прилично разработчиков в офисе в Индии и сделал вывод "откуда у этого процесса растут ноги".

Пока опущу часть самопрезентации, чтобы не перегружать статью и сфокусироваться на главном!

Интервью

Итак возвращаемся к основной теме и потенциальному работодателю этой публикации. Первое - было удержать себя и не послать куда подальше при предложении пройти отсев через hackerrank, второе - через тестовое задание. Я конечно рассказывал кадровику о своем опыте, кидал ссылки в письме на open source проекты и доклады но это вызывало лишь покерфейс и бубнение что "мы верим в наши процессы отбора, они помогают нам найти лучших работников". Вы-то верите в это, а кандидату скорее показывает что нет никакой гибкости в процессе найма и вам лень тратить свое время на переход на мои PR Github одного из 10K* проекта разрабатывамого под крылом Apache Software Foundation. Ладно "запишем на карандаш" и перейдем к следующему шагу.

Hackerrank тесты были простые: 8 заданий, общие вопросы по микросервисам, разработке ПО и одна задачка на что-то там алгоритмическое с картриджами. Сделал за 43 минуты из выделенных 45, при этом первые 3 минуты боролся с отвращением и мотивировал себя пройти через это "унижение" для матерого специалиста. Камон ребята, я же не выпускник коледжа по разработке программ из Бангалора. Что только не разрабатывал за свою карьеру и тем более активно участвовал в open source проектах в свое свободное время.

Задачка про картриджи:

Решение выбрал для выполнения на привычной мне Java и реализация прошла все тесты:

Поведенческое интервью с техническим руководителем мне понравилось тем что тут уже все было как обычно - рассказать о своем опыте, спросить про их проект. Сначала представился потенциальный руководитель Александр, рассказал о своем опыте на проекте Technology Compatibility Kit (TCK) для JavaME. Потом описал себя как профессионала в трех прилагательных. Потом была моя очередь, распушил хвост и рассказал о своем опыте, лучших проектах, релевантном опыте, где и как я боролся со сложностями в реальных проектах и потом без подготовки тоже выдал какие-то три рандомных и позитивных прилагательных о себе. Этот этап прошел успешно, обещали подумать пару дней но ответ что я успешно прошел это интервью пришел уже через несколько часов.

И вот тут снова пришло письмо с предложением выслать мне неоплачиваемое тестовое задание, как буду готов к его реализации.

We all have really enjoyed our conversations with you so far.

 You've
done great during the interviews, so we would like to move to the final
phase of the interview process with you — which is a Test Task.

This
is a technical task that could take about 4-6 hours to complete, this
estimation may vary depending on the candidate's availability and
skills. We have decided that it would be the best to confirm when you
are ready to start working on a project like that and send it by that
time, so we can expect to receive the results back in 1-3 days after we
send the task. Could you please let us know when would it be convenient
for you to receive the task from us?

I've also cc'ed Alexander in case you will have any technical questions when you receive the task.

Thanks for taking the time to go through our process, we're looking forward to hearing from you!

Я конечно попытался напомнить о том, что я уже говорил на HR интервью, чтобы избежать бесплатного программирования без какого-либо практического смысла для себя:

Thank you for good news. I'll have some time to implement Test Task tomorrow after 19:00 PM Maldivian time (GMT +5) but not with pleasure as from my point of view it looks like exercise for student without real experience and hiring process is not flexible enough... I have open for anyone strong proof of my work experience in top rated open source projects like Apache Arrow, Spring Framework/Boot, SchemaSpy, H2 etc

The most fresh example my Apache Arrow 10.0.0 (26 October 2022) contributions: 

https://github.com/apache/arrow/pulls?q=is%3Apr+author%3Aigor-suhorukov+is%3Aclosed

and in  https://arrow.apache.org/release/10.0.0.html

This technology using under the hood in many cutting edge Big Data frameworks and databases as Apache Spark and PySpark, Dremio, ClickHouse DB...

I hope that your company also shares common agile software methodology value "Individuals and interactions over processes and tools"

  Regards,

      Igor

На что был ответ:

Hi Igor, Hope your weekend was great.Yes, we do understand your point, but at the same time we need to evaluate all the candidates in the process fairly to each other and we need to compare the results with the same type of the task. Sure, I will send you the task by the time you.  Thank you and have a great rest of the day!

Тестовое задание

После того как я получил по почте следующий текст и тестовые данные в zip архиве, я засучил рукава и начал работать.

# Project:
It’s known that emails can have attachments, some even have other emails as attachments. This
allows them to have attachments at any level of nesting. Emails can be stored in so called
Internet Message Format, files in this format usually have .eml extension.

Let’s assume that someone could execute the following operations for arbitrary number of times
in a random order:
- Attach EML or ZIP file to the email
- Archive list of emails to the root folder of ZIP archive

So the sequence of operations is a storing format for the original set of files. For example ZIP
archive can contain emails with ZIP archive as attachment and the last one ZIP archives contain
emails with email as attachment (see samples provided).
  
# Requirements:
You need to write a CLI program that extracts files from the provided file based on the provided
format. See sample output for the sample above.
You must not guess input file format and the program should allow the user to specify input
format in some way, as a command line argument for example. Program should raise an error if
an input file cannot be converted based on the provided format.
For EML files handling it is highly recommended to use Java Mail library
( https://javaee.github.io/javamail ), MimeMessage, Multipart,BodyPart classes in particular.
For ZIP files handling it is recommended to use java.util.zip package from Java SE.
Solution should have quite good quality, which means it should be covered by tests, be able to
work with real-life emails (as this one) and quite large ZIP archives.

Код моего решения я выдал за 4.5 часа сразу же. Это при том что я еще задал пару вопросов по почте в самом начале после ознакомления с заданием, но из-за лимита времени приступил к следующим подзадачам:

  • Уговорить себя не отказаться сразу, а сделать неоплачиваемый тест. Почти час я боролся с собой и смог сфокусироваться только на задаче.

  • Разобраться с парсингом MIME формата сообщений и как сделать рекурсивную распаковку содержимого в ZIP.

Первым делом я добавил в проект тестовые данные из письма с заданием, потом начал искать зависимости на mvnrepository.com

Мое решение со всеми артефактами доступно на github. Основная логика задачи:

package com.github.isuhorukov.email;

import com.beust.jcommander.JCommander;
import org.apache.commons.io.IOUtils;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * My email attachment example for IoT system https://github.com/igor-suhorukov/alarm-system
 * also referenced from https://camel.apache.org/community/articles/
 */
public class EmailProcessor {

    public static final String CONTENT_TYPE = "Content-Type";
    public static final String MULTIPART_MIXED = "multipart/mixed";
    public static final String APPLICATION_X_ZIP_COMPRESSED = "application/x-zip-compressed";
    public static final String MESSAGE_RFC_822 = "message/rfc822";

    private final Session session = Session.getInstance(System.getProperties(), null);
    private final AtomicLong fileNameIncremental = new AtomicLong();

    public static void main(String[] args) throws Exception{
        final CLIParameters parameters = new CLIParameters();
        JCommander jCommander = JCommander.newBuilder().addObject(parameters).build();
        jCommander.parse(args);

        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.process(parameters);
    }

    void process(CLIParameters parameters) throws IOException, MessagingException {
        Objects.requireNonNull(parameters.getType(), "Expected source file type parameter");
        File sourceFile = FileUtils.getSourceFile(parameters);
        File outputDirectory = FileUtils.getOutputDirectory(parameters);
        switch (parameters.getType()){
            case ZIP:
                processZipArchive(sourceFile, outputDirectory);
                break;
            case EML:
                processMimeMessage(sourceFile, outputDirectory);
                break;
            default:
                throw new UnsupportedOperationException();
        }
    }

    void processZipArchive(File zipFile, File outputDirectory) throws IOException, MessagingException {
        if(!isZipStream(zipFile)){
            throw new IllegalArgumentException("ZIP stream magic sequence is not found");
        }
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile))){
            ZipEntry zipEntry;
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                if(zipEntry.isDirectory()){
                    throw new IllegalArgumentException("Only root files in zip archive supported");
                }
                File payload = File.createTempFile("payload", "eml");
                try (FileOutputStream payloadStream = new FileOutputStream(payload)){
                    IOUtils.copy(zipInputStream, payloadStream);
                    processMimeMessage(payload, outputDirectory);
                } finally {
                    if(payload.exists() && !payload.delete()){
                        throw new RuntimeException("It is not possible to delete temporary entry copy from zip");
                    }
                }
                zipInputStream.closeEntry();
            }
        }
    }

    void processMimeMessage(File payload, File outputDirectory) throws IOException, MessagingException {
        if(isZipStream(payload)){
            throw new IllegalArgumentException("Expected text content but found ZIP stream magic sequence");
        }
        try (FileInputStream mimeStream = new FileInputStream(payload)){
            MimeMessage message = new MimeMessage(session, mimeStream);
            if(message.isMimeType(MULTIPART_MIXED)) {
                MimeMultipart content = (MimeMultipart) message.getContent();
                int contentCount = content.getCount();
                for(int contentIndex = 0; contentIndex < contentCount; contentIndex++){
                    BodyPart bodyPart = content.getBodyPart(contentIndex);
                    if(bodyPart.getSize()>0){
                        String[] contentType = bodyPart.getHeader(CONTENT_TYPE);
                        if(contentType.length>0 && contentType[0].contains(APPLICATION_X_ZIP_COMPRESSED)){
                            File zipPayload = File.createTempFile("payload", "zip");
                            try (InputStream attachmentInStream = bodyPart.getInputStream();
                                 FileOutputStream zipOutStream = new FileOutputStream(zipPayload)){
                                IOUtils.copy(attachmentInStream, zipOutStream);
                                processZipArchive(zipPayload, outputDirectory);
                            } finally {
                                if(zipPayload.exists() && !zipPayload.delete()){
                                    throw new RuntimeException("It is not possible to delete temporary zip file");
                                }
                            }
                        } else if(contentType.length>0 && contentType[0].contains(MESSAGE_RFC_822)){
                            String[] contentTypeParts = contentType[0].split("\\n");
                            String resultFileName = FileUtils.getResultFileName(contentTypeParts, fileNameIncremental);
                            File outputFile = new File(outputDirectory, resultFileName);
                            if(outputFile.exists()){
                                do{
                                    outputFile = new File(outputDirectory, FileUtils.getSyntheticIncrementalName(fileNameIncremental.getAndIncrement()));
                                } while (outputFile.exists());
                                //throw new IllegalArgumentException("File already exists "+outputFile.getAbsolutePath());
                            }
                            MimeMessage mimeMessage = (MimeMessage) bodyPart.getContent();
                            try (FileOutputStream emlOutputStream = new FileOutputStream(outputFile)){
                                mimeMessage.writeTo(emlOutputStream);
                            }
                        }
                    }
                }
            }
        }
    }

    boolean isZipStream(File payload) throws IOException {
        try (FileInputStream fileInputStream = new FileInputStream(payload)){
            byte[] magicBytes = IOUtils.toByteArray(fileInputStream, 4);
            return Arrays.equals(magicBytes, new byte[]{80, 75, 3, 4});
        }
    }
}

Позволяет рекурсивно пройти по архивам, письмам в них и аттачментам в письмах( внутри которых так же могут быть архивы с письмами и так далее) и сохранить результат в директорию, при коллизии имен файла сохраняет в нумерованый файл с проверкой что такого еще не создано.

Аргументы командной строки разбираю с помощью JCommander и утилиту можно вызвать из одного jar артефакта с упакованными в него зависимостями.

Ну и конечно куда же без тестов. Код протестирован, но покрытие негативных сценариев минимальное, что успел за отведенное время.

EmailProcessorTest.java
package com.github.isuhorukov.email;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.util.Arrays;

import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class EmailProcessorTest {

    @Test
    void testMimeEmail1(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.processMimeMessage(new File(requireNonNull(EmailProcessor.class.
                getResource("/input/Email1.eml")).getFile()), tempDir);
        File[] files = tempDir.listFiles();
        assertThat(files.length).isEqualTo(1);
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
    }

    @Test
    void testMimeEmail2(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.processMimeMessage(new File(requireNonNull(EmailProcessor.class.
                getResource("/input/Email2.eml")).getFile()), tempDir);
        File[] files = tempDir.listFiles();
        Arrays.sort(files);
        assertThat(files.length).isEqualTo(2);
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test1.eml")).getFile()));
        assertThat(files[1]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test3.eml")).getFile()));
    }

    @Test
    void testNameCollisionSaveAll(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.processMimeMessage(new File(requireNonNull(EmailProcessor.class.
                getResource("/input/EmailDuplicate.eml")).getFile()), tempDir);
        File[] files = tempDir.listFiles();
        Arrays.sort(files);
        assertThat(files.length).isEqualTo(3);
        assertThat(files[0].getName()).isEqualTo("Test 2.eml");
        assertThat(files[1].getName()).isEqualTo("filename_collision_0.eml");
        assertThat(files[2].getName()).isEqualTo("filename_collision_1.eml");
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
        assertThat(files[1]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
    }

    @Test
    void testZipFile(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.processZipArchive(new File(requireNonNull(EmailProcessor.class.
                getResource("/input/archive.zip")).getFile()), tempDir);
        File[] files = tempDir.listFiles();
        Arrays.sort(files);
        assertThat(files.length).isEqualTo(3);
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test1.eml")).getFile()));
        assertThat(files[1]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
        assertThat(files[2]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test3.eml")).getFile()));
    }

    @Test
    void testProcessor(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        CLIParameters parameters = new CLIParameters();
        parameters.setSourcePath(requireNonNull(EmailProcessor.class.
                                    getResource("/input/archive.zip")).getFile());
        parameters.setOutputDir(tempDir.getAbsolutePath());
        parameters.setType(FileType.ZIP);

        emailProcessor.process(parameters);

        File[] files = tempDir.listFiles();
        Arrays.sort(files);
        assertThat(files.length).isEqualTo(3);
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test1.eml")).getFile()));
        assertThat(files[1]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
        assertThat(files[2]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test3.eml")).getFile()));
    }

    @Test
    void testProcessorNonValidTypeEml(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        CLIParameters parameters = new CLIParameters();
        parameters.setSourcePath(requireNonNull(EmailProcessor.class.
                                    getResource("/input/archive.zip")).getFile());
        parameters.setOutputDir(tempDir.getAbsolutePath());
        parameters.setType(FileType.EML);

        assertThrows(IllegalArgumentException.class, ()->emailProcessor.process(parameters));

    }

    @Test
    void testProcessorNonValidTypeZip(@TempDir File tempDir) throws Exception {
        EmailProcessor emailProcessor = new EmailProcessor();
        CLIParameters parameters = new CLIParameters();
        parameters.setSourcePath(requireNonNull(EmailProcessor.class.
                                    getResource("/input/Email1.eml")).getFile());
        parameters.setOutputDir(tempDir.getAbsolutePath());
        parameters.setType(FileType.ZIP);

        assertThrows(IllegalArgumentException.class, ()->emailProcessor.process(parameters));
    }

    @Test
    void testProcessorCliRun(@TempDir File tempDir) throws Exception {
        EmailProcessor.main(new String[]{"--source",requireNonNull(EmailProcessor.class.
                getResource("/input/archive.zip")).getFile(), "--output",tempDir.getAbsolutePath(), "--type", FileType.ZIP.toString()});

        File[] files = tempDir.listFiles();
        Arrays.sort(files);
        assertThat(files.length).isEqualTo(3);
        assertThat(files[0]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test1.eml")).getFile()));
        assertThat(files[1]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test2.eml")).getFile()));
        assertThat(files[2]).hasSameTextualContentAs(new File(requireNonNull(EmailProcessorTest.class.
                getResource("/output/test3.eml")).getFile()));
    }
}

Отмечу позитивный момент, что мне понравилось общаться с интервьюверами. Поведенческое интервью было только с позитивными кейсами и очень дружелюбное по форме. С этими людьми было легко и приятно общаться. После отказа я запросил звонок, чтобы мне объяснили какое же решение задачи на самом деле ожидали от меня.

Could you please make call with me to discuss what do you means as "Storing format is a sequence of operations". I asked about this points from first questions for this test task. Now for me it looks like not well defined requirements for 4-6h test exercise.

As I am invested my unpayable time for software development I expect detailed review why this solutions is not fit and what are you expect as a result

   Regards,

         Igor

И в ответ мне назначили созвон с интервьювером Александром, где я смог голосом уточнить детали. Обычно все после отказа теряются и не выходят на связь. То что ответил на мои вопросы - реально располагает и заслуживает уважения!

Результат

Вчера мы созвонились и то что выяснил в диалоге, с моей точки зрения сильно выходит за 4-6 часов на задачу, с учетом покрытия тестами запрашиваемой реализации и ее сложности. Я вообще считаю что фактор стресса из-за ограниченности по времени решения, непонятности деталей задачи могут понизить производительность, как и ежедневный отдых и дайвинг с 19 сентября у океана. Эта ситуация отдаленно мне напомнила кадры из фильма Password Swordfish когда Джон Траволта тестирует профпригодность и стрессоустойчивость Хью Майкла Джекмана. Но что точно могу сказать что хорошо что когда-то давно я уже сталкивался с MIME форматом и мне не пришлось погружаться в его детали впервые. Так что IMHO в оценках времени на задачу компания слукавила.

Да, конечно же задание сформулировано так, что то что на самом деле хотели и имелось в виду, человеку не сталкивавшемуся с этим тестом и не получится угадать. С первых же минут после получения задания я писал в почте уточняющие вопросы и реагировал на замечания с учетом новых вводных данных. Дважды отправлял правки кода и тестов в процессе.

Кому интересны детали для понимания уточнений по заданию. Вдруг будет полезно

9 ноября 2022, 2:34

Hello Igor,

Thanks again you for your efforts to complete the test task.

Several team members on our end have reviewed it and compared your solution to the other submissions we received, so we are coming back to you with the decision. Unfortunately, we have decided to move forward with another candidate at this time.

You have delivered an appropriate solution to our test task, but there is one essential drawback from our point of view - task solves slightly a different problem than was asked - it recursively extracts files based on the file type instead of extracting files based on the storing format. Storing format is a sequence of operations so it cannot be represented by just two options.

We enjoyed collaboration with you through the process and would be happy to keep in touch for the future opportunities.

Thanks again, have a nice day!

3 ноября 2022, 12:42

Hi Alexander,

Thank you for details. Please find updated code in attachment as I am understood tool behavior from your email.

Regards,
Igor

3 ноября 2022, 16:39

Hi Igor,

lets consider I've a message "Fwd Test 2.eml" and I zipped it (both attached).
I'd like to receive Fwd Test 2.eml with attachment in one run and Test 2.eml
in another run by passing the same ZIP archive and different formats.

As it's written in task you must not guess input file format and format is a sequence of operations but not file type.

Thanks,
Alexander

3 ноября 2022, 00:19

Hi Alexander,

Sorry, still don't understand your point. Could you please provide examples/test data for this cases or provide format parameter examples for CLI. Is it something like script with ordinal number of MIME section & corresponding action or something like xPath expression to select nested nodes?

I'll be verify appreciated for more input data and expected results for it. Need more clarification before I'll start

Regards, Igor

3 ноября 2022, 05:19
Hi Igor,

I've started looking into the code and found out it's not possible to provide storing format of the input file but just type of the file.
Format is a sequence of operations but not file type. Currently it's not possible to distinguish ordinal email with rfc822 attachment
and email whose attachment should be unpacked.

Will you be able to fix the solution?

Thanks,
Alexander

2 ноября 2022, 09:39

Hello Igor,

thanks for submitting, will get back to you in couple of days

Thanks,
Alexander

2 ноября 2022, 00:08

Good morning Alexander,

Now I implement sequential file naming and add unit test for it.

Regards,
Igor

1 ноября 2022, 21:08
Hi Igor,

any file name collision resolution is accepted, the only requirement do not lose files.

Thanks,
Alexander

1 ноября 2022, 12:29

Ok, got it! Thanks.
How are you expect result message file naming:
synthetic file name from increasing counter or file name from Content-type. In case of second options how to handle possible file naming collision?

Regards,
Igor

1 ноября 2022, 19:07
Hi Igor,

the idea that format could be arbitrary. Not just attachments in mails in ZIP archive but
could be as simple as single email. You don't need to make any conversion in this case.
So one of the task for you to design format's specification, it could be passed through
command line or any other convenient way.

Thanks,
Alexander

1 ноября 2022, 10:38

Hi Alexander,

What do you means:
You must not guess input file format and the program should allow the user to specify input
format in some way, as a command line argument for example

Could you please provide examples of command line for this program?

Is the final goal to extract all nested messages from each ZIP attachment recursively?

Regards,
Igor

Хоть я и не прошел тестовое задание в эту компанию и инвестировал суммарно день своей жизни на все процессы интервью, я рад что все так как произошло. Конечно, успешное выполнение задания больше подняло мою самооценку чем итоговый отказ, но точно создало бы дилемму. Были у меня сомнения что бизнес компании не очень этичный с моей точки зрения и что я торгуюсь со своей совестью и переступаю через себя в доказательствах что я опытный специалист с подтверждаемым и доступным для ознакомления опытом в open source проектах. Но если же кто нибудь пойдет этому же пути, у вас будет больше информации для прохождения процесса интервью и принятия окончательного решения.

Поиск работы последние полтора месяца для меня показал что:

  • Похоже hh.ru уже в запустении по вакансиям и компаниям, кто готов оплачивать поиск на этом ресурсе.

  • Мой LinkedIn профиль с февраля этого года в затишье. Раньше он был постоянным фоновым источником вакансий и информации о состоянии рынка разработки ПО.

  • По позициям синьорных разработчиков и тимлидов теперь отсев по leetcode, hackerrank и много где решение алгоритмических задач.

Пишите в комментариях ваши предположения о том что в моем варианте выполнения задания нужно было сделать иначе и чего там не хватает. Интересно как другие разработчики интерпретируют то же описание и достаточно ли в нем информации для решения. Ответы Александра и дополнительные примеры в почте не прояснили для меня а что же такое "sequence of operations is a storing format" Вы так же можете сделать PR на github решения тестового задания чтобы исправить его так, как ожидалось. Этим вы можете лучше любых слов помочь кому-либо пройти это же задание в будущем или же лучше подготовиться к интервью!

Upd: 17.11.22 22:26

Сегодня на закате пошел ливень и у меня появилось время чтобы завершить это тестовое задание. Тогда по общению с Александром голосом понял, что нужна какая-то конфигурация через которую бы указывалось программе какие именно вложенные письма сохранить. Я предложил аналогию xPath выражений, что так же должно работать. Теперь при запуске с параметром --opsequence unzip/0/1/unzip/0/1,unzip/1/1/unzip/0/1,unzip/1/1/unzip/1/1 программа позволяет извлечь и сохранить три письма по указанным путям(попутно распаковывая вложения как и корневой архив), так же как и в случае если что-то из конфигурации не нашлось - бросить ошибку и указать какие пути не обнаружены.

Обновленный код и тесты в том же репозитарии. На это я потратил еще 4.5 часа. Итого 9 часов на весь кодинг. В репозитарии еще есть что шлифовать с точки зрения тестов(95% методов, 87% строчек уже покрыты тестами) и качества кода. Это все не укладывается в заявленные 4-6 часа неоплачиваемого тестового задания по сложности реализации. Надеюсь, что кому-нибудь это все же пригодиться в прохождении...

Теги:
Хабы:
Всего голосов 37: ↑28 и ↓9+19
Комментарии72

Публикации

Истории

Работа

Java разработчик
350 вакансий

Ближайшие события