Repository: societe-generale/github-crawler
Pull request #94 (2022-03-20 18:09:23)

Crawling Azure devops repositories


Patch:
diff --git a/github-crawler-autoconfigure/pom.xml b/github-crawler-autoconfigure/pom.xml
index eb4b8fd..ca8f31a 100644
--- a/github-crawler-autoconfigure/pom.xml
+++ b/github-crawler-autoconfigure/pom.xml
@@ -37,6 +37,13 @@
             </exclusions>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+            <version>2.32.0</version>
+            <scope>test</scope>
+        </dependency>
+
 
         <dependency>
             <groupId>com.jayway.awaitility</groupId>
diff --git a/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/AzureDevopsConfiguration.kt b/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/AzureDevopsConfiguration.kt
new file mode 100644
index 0000000..ab32083
--- /dev/null
+++ b/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/AzureDevopsConfiguration.kt
@@ -0,0 +1,38 @@
+package com.societegenerale.githubcrawler.config
+
+
+import com.societegenerale.githubcrawler.GitHubCrawlerProperties
+import com.societegenerale.githubcrawler.remote.RemoteAzureDevopsImpl
+import com.societegenerale.githubcrawler.remote.RemoteAzureDevopsImpl.Companion.AZURE_DEVOPS_URL
+import com.societegenerale.githubcrawler.remote.RemoteGitHub
+import org.slf4j.LoggerFactory
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+@ConditionalOnProperty(prefix = "github-crawler.githubConfig", name = ["type"], havingValue = "AZURE_DEVOPS")
+open class AzureDevopsConfiguration {
+
+    val log = LoggerFactory.getLogger(this.javaClass)
+
+    @Bean
+    open fun remoteAzureDevops(gitHubCrawlerProperties: GitHubCrawlerProperties): RemoteGitHub {
+
+        val targetUrl:String
+
+        if(gitHubCrawlerProperties.githubConfig.apiUrl.isEmpty()){
+            targetUrl=  AZURE_DEVOPS_URL
+        }
+        else{
+            targetUrl=gitHubCrawlerProperties.githubConfig.apiUrl
+        }
+
+        log.info("URL for AzureDevops : $targetUrl")
+
+        return RemoteAzureDevopsImpl(targetUrl,
+                                     gitHubCrawlerProperties.githubConfig.organizationName,
+                                     gitHubCrawlerProperties.githubConfig.oauthToken)
+    }
+
+}
diff --git a/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/GitHubCrawlerAutoConfiguration.kt b/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/GitHubCrawlerAutoConfiguration.kt
index d8a3c1f..aea779d 100644
--- a/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/GitHubCrawlerAutoConfiguration.kt
+++ b/github-crawler-autoconfigure/src/main/kotlin/com/societegenerale/githubcrawler/config/GitHubCrawlerAutoConfiguration.kt
@@ -1,29 +1,27 @@
 package com.societegenerale.githubcrawler.config
 
 
-import com.societegenerale.githubcrawler.ConfigValidator
-import com.societegenerale.githubcrawler.GitHubCrawler
-import com.societegenerale.githubcrawler.GitHubCrawlerProperties
-import com.societegenerale.githubcrawler.RepositoryEnricher
+import com.societegenerale.githubcrawler.*
 import com.societegenerale.githubcrawler.output.GitHubCrawlerOutput
 import com.societegenerale.githubcrawler.parsers.FileContentParser
 import com.societegenerale.githubcrawler.remote.RemoteGitHub
-import com.societegenerale.githubcrawler.remote.RemoteGitHubImpl
-import com.societegenerale.githubcrawler.remote.RemoteGitLabImpl
 import com.societegenerale.githubcrawler.repoTaskToPerform.RepoTaskBuilder
+import org.slf4j.LoggerFactory
 import org.springframework.boot.context.properties.EnableConfigurationProperties
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.context.annotation.Import
-import org.springframework.context.annotation.Profile
 import org.springframework.core.convert.ConversionService
 import org.springframework.core.env.Environment
 
 @Configuration
-@Import(GitHubCrawlerParserConfig::class,GitHubCrawlerOutputConfig::class,GitHubCrawlerMiscTasksConfig::class,GitHubConfiguration::class,GitLabConfiguration::class)
+@Import(GitHubCrawlerParserConfig::class,GitHubCrawlerOutputConfig::class,GitHubCrawlerMiscTasksConfig::class,
+    GitHubConfiguration::class,GitLabConfiguration::class,AzureDevopsConfiguration::class)
 @EnableConfigurationProperties(GitHubCrawlerProperties::class)
 open class GitHubCrawlerAutoConfiguration {
 
+    val log = LoggerFactory.getLogger(this.javaClass)
+
     @Bean
     open fun conversionService(): ConversionService {
         //TODO custom converter should be an annotation if possible - right now, it works because we instantiate a bean named conversionService
@@ -38,12 +36,16 @@ open class GitHubCrawlerAutoConfiguration {
                      environment : Environment,
                      configValidator: ConfigValidator,
                      fileContentParsers: List<FileContentParser>,
-                     repoTasksBuilder: List<RepoTaskBuilder>
+                     repoTasksBuilders: List<RepoTaskBuilder>
                      ): GitHubCrawler {
 
-        val repositoryEnricher = RepositoryEnricher(remoteGitHub)
+        var availableParsersAndTasks = AvailableParsersAndTasks(fileContentParsers,repoTasksBuilders)
+
+        val repositoryEnricher = RepositoryEnricher(remoteGitHub,availableParsersAndTasks)
+
+        log.info("using repositoryEnricher "+repositoryEnricher+" when building the crawler...")
 
-        return GitHubCrawler(remoteGitHub, output, repositoryEnricher,gitHubCrawlerProperties,environment,gitHubCrawlerProperties.githubConfig.organizationName,configValidator,fileContentParsers,repoTasksBuilder)
+        return GitHubCrawler(remoteGitHub, output, repositoryEnricher,gitHubCrawlerProperties,environment,gitHubCrawlerProperties.githubConfig.organizationName,configValidator,availableParsersAndTasks)
     }
 
     @Bean
diff --git a/github-crawler-autoconfigure/src/test/resources/branches.json b/github-crawler-autoconfigure/src/test/resources/github/branches.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/branches.json
rename to github-crawler-autoconfigure/src/test/resources/github/branches.json
diff --git a/github-crawler-autoconfigure/src/test/resources/dummyFileOnRepo.json b/github-crawler-autoconfigure/src/test/resources/github/dummyFileOnRepo.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/dummyFileOnRepo.json
rename to github-crawler-autoconfigure/src/test/resources/github/dummyFileOnRepo.json
diff --git a/github-crawler-autoconfigure/src/test/resources/organisation.json b/github-crawler-autoconfigure/src/test/resources/github/organisation.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/organisation.json
rename to github-crawler-autoconfigure/src/test/resources/github/organisation.json
diff --git a/github-crawler-autoconfigure/src/test/resources/pomXmlFileOnRepo.json b/github-crawler-autoconfigure/src/test/resources/github/pomXmlFileOnRepo.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/pomXmlFileOnRepo.json
rename to github-crawler-autoconfigure/src/test/resources/github/pomXmlFileOnRepo.json
diff --git a/github-crawler-autoconfigure/src/test/resources/searchResult.json b/github-crawler-autoconfigure/src/test/resources/github/searchResult.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/searchResult.json
rename to github-crawler-autoconfigure/src/test/resources/github/searchResult.json
diff --git a/github-crawler-autoconfigure/src/test/resources/template_FileOnRepo.json b/github-crawler-autoconfigure/src/test/resources/github/template_FileOnRepo.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/template_FileOnRepo.json
rename to github-crawler-autoconfigure/src/test/resources/github/template_FileOnRepo.json
diff --git a/github-crawler-autoconfigure/src/test/resources/template_FileOnRepo_withSubDir.json b/github-crawler-autoconfigure/src/test/resources/github/template_FileOnRepo_withSubDir.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/template_FileOnRepo_withSubDir.json
rename to github-crawler-autoconfigure/src/test/resources/github/template_FileOnRepo_withSubDir.json
diff --git a/github-crawler-autoconfigure/src/test/resources/gitLabGroups.json b/github-crawler-autoconfigure/src/test/resources/gitlab/gitLabGroups.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/gitLabGroups.json
rename to github-crawler-autoconfigure/src/test/resources/gitlab/gitLabGroups.json
diff --git a/github-crawler-autoconfigure/src/test/resources/gitLabRepoSearchResults.json b/github-crawler-autoconfigure/src/test/resources/gitlab/gitLabRepoSearchResults.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/gitLabRepoSearchResults.json
rename to github-crawler-autoconfigure/src/test/resources/gitlab/gitLabRepoSearchResults.json
diff --git a/github-crawler-autoconfigure/src/test/resources/gitLabRepositories.json b/github-crawler-autoconfigure/src/test/resources/gitlab/gitLabRepositories.json
similarity index 100%
rename from github-crawler-autoconfigure/src/test/resources/gitLabRepositories.json
rename to github-crawler-autoconfigure/src/test/resources/gitlab/gitLabRepositories.json
diff --git a/github-crawler-core/pom.xml b/github-crawler-core/pom.xml
index 78df50c..1300006 100644
--- a/github-crawler-core/pom.xml
+++ b/github-crawler-core/pom.xml
@@ -20,7 +20,7 @@
         <!--
             Theoretically, we don't need to declare gson lib, as it's provided by spring-cloud-starter-openfeign .
             The gson version is overridden in spring boot starter (to 2.8.5 as of Spring Boot 2.0.4)
-            However, when starter is built and used elsewhere, the version switches back to th eone defined in spring-cloud-starter-openfeign
+            However, when starter is built and used elsewhere, the version switches back to the one defined in spring-cloud-starter-openfeign
             and it doesn't work
 
             more infos : https://stackoverflow.com/questions/52143687/not-getting-the-expected-version-when-using-the-spring-boot-starter-i-built
@@ -94,9 +94,15 @@
         <dependency>
             <groupId>com.squareup.okhttp3</groupId>
             <artifactId>okhttp</artifactId>
-            <version>3.10.0</version>
         </dependency>
 
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>logging-interceptor</artifactId>
+        </dependency>
+
+
         <dependency>
             <groupId>com.jayway.awaitility</groupId>
             <artifactId>awaitility</artifactId>
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/AvailableParsersAndTasks.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/AvailableParsersAndTasks.kt
new file mode 100644
index 0000000..634639e
--- /dev/null
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/AvailableParsersAndTasks.kt
@@ -0,0 +1,49 @@
+package com.societegenerale.githubcrawler
+
+import com.societegenerale.githubcrawler.parsers.FileContentParser
+import com.societegenerale.githubcrawler.repoTaskToPerform.RepoTaskBuilder
+import org.slf4j.LoggerFactory
+
+class AvailableParsersAndTasks(fileContentParsers: List<FileContentParser>,
+                               repoTasksBuilders: List<RepoTaskBuilder>) {
+
+
+  val log = LoggerFactory.getLogger(this.javaClass)
+
+  //TODO validate we don't have 2 parsers with same nameInConfig
+
+  private val availableFileContentParsers = fileContentParsers.map{it.getNameInConfig() to it}.toMap()
+
+  private val availableRepoTasksBuilders = repoTasksBuilders.map{it.type to it}.toMap()
+
+  init{
+    log.info("--> ${availableFileContentParsers.size} available parser(s)..")
+    log.info("--> ${availableRepoTasksBuilders.size} taskBuilder(s) available : "+availableRepoTasksBuilders.keys.joinToString(separator = ", "))
+  }
+
+  fun getParserByName(parserName : String) : FileContentParser{
+    val parser= availableFileContentParsers.get(parserName)
+
+    if(parser==null){
+      throw IllegalStateException("fileContentParserName is unknown : $parserName. please double check your config, the name must match one of the known values")
+    }
+    else{
+      return parser
+    }
+  }
+
+  fun getRepoTasksBuilderByType(repoTasksBuilderType : String) : RepoTaskBuilder{
+    val repoTasksBuilder= availableRepoTasksBuilders.get(repoTasksBuilderType)
+
+    if(repoTasksBuilder==null){
+      throw IllegalStateException("repoTasksBuilderType is unknown : $repoTasksBuilderType. please double check your config, the type must match one of the known values")
+    }
+    else{
+      return repoTasksBuilder
+    }
+  }
+
+
+
+
+}
\ No newline at end of file
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/ConfigValidator.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/ConfigValidator.kt
index 2625002..1dbfebc 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/ConfigValidator.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/ConfigValidator.kt
@@ -17,7 +17,9 @@ class ConfigValidator(val properties : GitHubCrawlerProperties,
 
         val validationErrors = mutableListOf<String>()
 
-        if(properties.githubConfig.apiUrl.isBlank()){
+        // there's a default url for AzureDevops, no need to configure it
+        if(properties.githubConfig.type!= SourceControlType.AZURE_DEVOPS &&
+           properties.githubConfig.apiUrl.isBlank()){
             validationErrors.add("gitHub.apiUrl can't be empty")
         }
 
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GitHubCrawler.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GitHubCrawler.kt
index 81618dd..35c05ce 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GitHubCrawler.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GitHubCrawler.kt
@@ -2,18 +2,14 @@ package com.societegenerale.githubcrawler
 
 import com.societegenerale.githubcrawler.model.Repository
 import com.societegenerale.githubcrawler.output.GitHubCrawlerOutput
-import com.societegenerale.githubcrawler.parsers.FileContentParser
 import com.societegenerale.githubcrawler.remote.RemoteGitHub
-import com.societegenerale.githubcrawler.repoTaskToPerform.RepoTaskBuilder
 import com.societegenerale.githubcrawler.repoTaskToPerform.RepoTaskToPerform
 import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.core.env.Environment
 import java.io.IOException
 import java.text.SimpleDateFormat
 import java.util.*
 import java.util.stream.Collectors.toList
-import kotlin.collections.ArrayList
 
 class GitHubCrawler(private val remoteGitHub: RemoteGitHub,
                     private val output: List<GitHubCrawlerOutput>,
@@ -22,19 +18,15 @@ class GitHubCrawler(private val remoteGitHub: RemoteGitHub,
                     private val environment: Environment,
                     private val organizationName: String,
                     private val configValidator: ConfigValidator,
-                    @Autowired
-                    private val fileContentParsers: List<FileContentParser> = emptyList(),
-                    @Autowired
-                    private val taskBuilders: List<RepoTaskBuilder> = emptyList()) {
+                    private val availableParsersAndTasks : AvailableParsersAndTasks) {
 
     companion object {
         const val NO_CRAWLER_RUN_ID_DEFINED: String = "NO_CRAWLER_RUN_ID_DEFINED"
-        val availableFileContentParsers = HashMap<String, FileContentParser>()
-        val tasksToPerform = ArrayList<RepoTaskToPerform>()
     }
 
     val log = LoggerFactory.getLogger(this.javaClass)
 
+    val tasksToPerform = ArrayList<RepoTaskToPerform>()
 
     @Throws(IOException::class)
     fun crawl() {
@@ -45,8 +37,12 @@ class GitHubCrawler(private val remoteGitHub: RemoteGitHub,
             throw IllegalStateException("There are some config validation errors - please double check the config. \n" + configValidationErrors.joinToString(separator = "\n", prefix = "\t - "))
         }
 
-        if (availableFileContentParsers.isEmpty() || tasksToPerform.isEmpty()) {
-            initParsersConfig()
+        gitHubCrawlerProperties.miscRepositoryTasks.forEach{
+
+            val matchingTaskBuilder=availableParsersAndTasks.getRepoTasksBuilderByType(it.type)
+
+            tasksToPerform.add(matchingTaskBuilder.buildTask(it.name, it.params))
+
         }
 
         var repositoriesFromOrga = remoteGitHub.fetchRepositories(organizationName)
@@ -67,38 +63,6 @@ class GitHubCrawler(private val remoteGitHub: RemoteGitHub,
         return gitHubCrawlerProperties
     }
 
-    private fun initParsersConfig() {
-
-        //TODO validate we don't have 2 parsers with same nameInConfig
-
-        //TODO check that all methods in gitHubCrawlerProperties are known, ie mappable to a Parser.
-        // Otherwise, we'll get an NPE later during processing
-
-        fileContentParsers.forEach {
-            log.info("adding ${it.getNameInConfig()} in the map of available file parsers..")
-            availableFileContentParsers.put(it.getNameInConfig(), it)
-        }
-        log.info("--> ${availableFileContentParsers.size} available parser(s)..")
-
-        log.info("${gitHubCrawlerProperties.miscRepositoryTasks.size} task(s) to create...")
-        val availableTaskBuilderTypes=taskBuilders.map { task -> task.type }.joinToString(separator = ", ")
-        log.info("${taskBuilders.size} taskBuilder(s) available : $availableTaskBuilderTypes")
-
-        gitHubCrawlerProperties.miscRepositoryTasks.forEach{
-
-            val matchingTaskBuilder=taskBuilders.find { taskBuilder -> taskBuilder.type.equals(it.type) }
-
-            if(matchingTaskBuilder==null){
-                log.warn("task with type ${it.type} couldn't be found in list of available task builders")
-            }
-            else {
-                tasksToPerform.add(matchingTaskBuilder.buildTask(it.name, it.params))
-            }
-        }
-
-        log.info("--> ${tasksToPerform.size} task(s) to perform have been built..")
-
-    }
 
     @Throws(IOException::class)
     private fun fetchAndParseRepoContent(repositoriesFromOrga: Set<Repository>) {
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GithubConfig.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GithubConfig.kt
index 1eedb36..3c5e23f 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GithubConfig.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/GithubConfig.kt
@@ -11,5 +11,5 @@ class GithubConfig(var type: SourceControlType = SourceControlType.GITHUB,
                    var crawlUsersRepoInsteadOfOrgasRepos: Boolean=false)
 
 enum class SourceControlType {
-  GITLAB, GITHUB
+  GITLAB, GITHUB,AZURE_DEVOPS
 }
\ No newline at end of file
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/RepositoryEnricher.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/RepositoryEnricher.kt
index 84e9d5a..3d95d05 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/RepositoryEnricher.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/RepositoryEnricher.kt
@@ -9,7 +9,8 @@ import com.societegenerale.githubcrawler.repoTaskToPerform.RepoTaskToPerform
 import org.slf4j.LoggerFactory
 
 
-class RepositoryEnricher(val remoteGitHub: RemoteGitHub){
+class RepositoryEnricher(val remoteGitHub: RemoteGitHub,
+                         val availableParsersAndTasks : AvailableParsersAndTasks = AvailableParsersAndTasks(emptyList(), emptyList())){
 
     val log = LoggerFactory.getLogger(this.javaClass)
 
@@ -116,7 +117,7 @@ class RepositoryEnricher(val remoteGitHub: RemoteGitHub){
     private fun parseIndicatorsFromFileContent(fileContent: String, pathToFileToGetIndicatorsFrom:String, indicatorsToFetch: List<IndicatorDefinition>): Map<String, String> {
 
         return indicatorsToFetch.asSequence()
-                .map { GitHubCrawler.availableFileContentParsers.get(it.type)!!.parseFileContentForIndicator(fileContent, pathToFileToGetIndicatorsFrom, it) }
+                .map { availableParsersAndTasks.getParserByName(it.type).parseFileContentForIndicator(fileContent, pathToFileToGetIndicatorsFrom, it) }
                 .reduce { acc, item -> acc + item }
 
     }
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/AzureDevopsRepository.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/AzureDevopsRepository.kt
new file mode 100644
index 0000000..aee2e2f
--- /dev/null
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/AzureDevopsRepository.kt
@@ -0,0 +1,7 @@
+package com.societegenerale.githubcrawler.model.azuredevops
+
+class AzureDevopsRepository(val name:String,
+                            val url : String,
+                            val defaultBranch : String) {
+
+}
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/Repositories.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/Repositories.kt
new file mode 100644
index 0000000..54cd3fe
--- /dev/null
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/model/azuredevops/Repositories.kt
@@ -0,0 +1,4 @@
+package com.societegenerale.githubcrawler.model.azuredevops
+
+class Repositories(val value : List<AzureDevopsRepository>) {
+}
\ No newline at end of file
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteAzureDevopsImpl.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteAzureDevopsImpl.kt
new file mode 100644
index 0000000..0418d48
--- /dev/null
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteAzureDevopsImpl.kt
@@ -0,0 +1,208 @@
+package com.societegenerale.githubcrawler.remote
+
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.societegenerale.githubcrawler.RepositoryConfig
+import com.societegenerale.githubcrawler.model.Branch
+import com.societegenerale.githubcrawler.model.PullRequest
+import com.societegenerale.githubcrawler.model.Repository
+import com.societegenerale.githubcrawler.model.SearchResult
+import com.societegenerale.githubcrawler.model.azuredevops.Repositories
+import com.societegenerale.githubcrawler.model.commit.Commit
+import com.societegenerale.githubcrawler.model.commit.DetailedCommit
+import com.societegenerale.githubcrawler.model.team.Team
+import com.societegenerale.githubcrawler.model.team.TeamMember
+import okhttp3.Credentials
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.apache.commons.io.IOUtils
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import java.io.IOException
+import java.io.StringWriter
+import java.util.*
+import java.util.stream.Collectors.toSet
+
+
+class RemoteAzureDevopsImpl @JvmOverloads constructor(val azureDevopsUrl: String = "https://dev.azure.com/", val organization: String, val personalAccessToken: String) : RemoteGitHub {
+
+    var log = LoggerFactory.getLogger(this.javaClass.toString())
+
+    companion object {
+
+        const val AZURE_DEVOPS_URL= "https://dev.azure.com/"
+
+        const val AZURE_DEVOPS_API_VERSION= "api-version=7.1-preview.1"
+
+        const val REPO_LEVEL_CONFIG_FILE = ".azureDevopsCrawler"
+
+    }
+
+    private val splitedOrgName=organization.split("#")
+    private var azureOrg=splitedOrgName.get(0)
+    private var azureProject=splitedOrgName.get(1)
+
+    private val objectMapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+
+    private val basicAuthentCredentials: String = Credentials.basic("", personalAccessToken)
+
+    private val requestTemplate=okhttp3.Request.Builder()
+        .header("Content-Type", "application/json")
+        .header("Authorization", basicAuthentCredentials)
+
+    private val httpClient : OkHttpClient
+
+    init {
+        val logging= HttpLoggingInterceptor()
+        //logging.level = (HttpLoggingInterceptor.Level.HEADERS)
+
+        httpClient= OkHttpClient.Builder().addInterceptor(logging).build()
+    }
+
+    override fun fetchRepositories(organizationName: String): Set<Repository> {
+
+        log.info("fetch repositories from "+this.toString());
+
+        val repositoriesUrl=azureDevopsUrl+"${azureOrg}/${azureProject}/_apis/git/repositories?${AZURE_DEVOPS_API_VERSION}"
+
+        val request = requestTemplate.url(repositoriesUrl).build()
+
+        val responseBody=httpClient.newCall(request).execute().body()
+
+        val repositories = objectMapper.readValue(responseBody?.string(), Repositories::class.java)
+
+        return repositories.value.stream().map{ repo -> Repository(
+            url=repo.url,
+            name =repo.name,
+            fullName =repo.name,
+            defaultBranch = repo.defaultBranch,
+            //TODO make the dates nullable in Repository
+            creationDate = Date(),
+            lastUpdateDate = Date(),
+        )}
+            .collect(toSet())
+
+    }
+
+
+    override fun fetchRepoConfig(repositoryFullName: String, defaultBranch: String): RepositoryConfig {
+
+        val configUrl=azureDevopsUrl+"${azureOrg}/${azureProject}/_apis/git/repositories/${repositoryFullName}/items?path=${REPO_LEVEL_CONFIG_FILE}&${
+            AZURE_DEVOPS_API_VERSION}"
+
+        log.info("fetching code search result from $configUrl");
+
+
+        val request = requestTemplate.url(configUrl).build()
+
+        val response=httpClient.newCall(request).execute()
+
+        val decoder = AzureDevopsResponseDecoder()
+
+        return decoder.decodeRepoConfig(response)
+    }
+
+    override fun fetchRepoBranches(repositoryFullName: String): Set<Branch> {
+        TODO("Not yet implemented")
+    }
+
+    override fun fetchCodeSearchResult(repositoryFullName: String, query: String): SearchResult {
+        TODO("Not yet implemented")
+    }
+
+    override fun fetchFileContent(repositoryFullName: String, branchName: String, fileToFetch: String): String {
+        val fileContentUrl=azureDevopsUrl+"$azureOrg/$azureProject/_apis/git/repositories/$repositoryFullName/items?" +
+            "path=${fileToFetch}" +
+            "&versionDescriptor.versionType=branch" +
+            "&versionDescriptor.version=" +extractBranchNameFromRef(branchName) +
+            "&${AZURE_DEVOPS_API_VERSION}"
+
+        val request = requestTemplate.url(fileContentUrl).build()
+
+        val response=httpClient.newCall(request).execute()
+
+        if(response.isSuccessful){
+            return response.body()?.string() ?: ""
+        }
+        else{
+            throw NoFileFoundException("can't find $fileToFetch in repo $repositoryFullName, in branch $branchName")
+        }
+
+    }
+
+    /**
+     * the branchName we receive is actually the reference, like "refs/heads/main". We need only the part after the "/"
+     */
+    private fun extractBranchNameFromRef(refName: String): String {
+        return refName.substring(refName.lastIndexOf("/")+1)
+    }
+
+    override fun fetchCommits(repositoryFullName: String, perPage: Int): Set<Commit> {
+        TODO("Not yet implemented")
+    }
+
+    override fun fetchCommit(repositoryFullName: String, commitSha: String): DetailedCommit {
+        TODO("Not yet implemented")
+    }
+
+    override fun fetchTeams(organizationName: String): Set<Team> {
+        TODO("Not yet implemented")
+    }
+
+    override fun fetchTeamsMembers(teamId: String): Set<TeamMember> {
+        TODO("Not yet implemented")
+    }
+
+
+
+    override fun validateRemoteConfig(organizationName: String) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun fetchOpenPRs(repositoryFullName: String): Set<PullRequest> {
+        TODO("Not yet implemented")
+    }
+
+
+}
+
+internal class AzureDevopsResponseDecoder {
+    val log = LoggerFactory.getLogger(this.javaClass)
+
+    val repoConfigMapper = ObjectMapper(YAMLFactory())
+
+    init {
+        repoConfigMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+        repoConfigMapper.registerModule(KotlinModule.Builder().build())
+    }
+
+    fun decodeRepoConfig(response: okhttp3.Response): RepositoryConfig {
+
+        if(response.code()==HttpStatus.NOT_FOUND.value()){
+            return RepositoryConfig()
+        }
+
+        val writer = StringWriter()
+        IOUtils.copy(response.body()?.byteStream(), writer, "UTF-8")
+        val responseAsString = writer.toString()
+
+        return parseRepositoryConfigResponse(responseAsString, response)
+    }
+
+    private fun parseRepositoryConfigResponse(responseAsString: String, response: okhttp3.Response): RepositoryConfig {
+        if (responseAsString.isEmpty()) {
+            return RepositoryConfig()
+        }
+
+        try {
+            return repoConfigMapper.readValue(responseAsString, RepositoryConfig::class.java)
+        } catch (e: IOException) {
+            throw Repository.RepoConfigException(HttpStatus.BAD_REQUEST,"unable to parse config for repo - content : \"" + response.body() + "\"", e)
+        }
+    }
+
+}
+
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitHubImpl.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitHubImpl.kt
index 647d02f..10d1602 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitHubImpl.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitHubImpl.kt
@@ -45,8 +45,6 @@ import java.lang.reflect.Type
 @Suppress("TooManyFunctions") // most of methods are one liners, implementing the methods declared in interface
 class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val usersReposInsteadOfOrgasRepos: Boolean = false, val oauthToken: String) : RemoteGitHub {
 
-
-
     companion object {
         const val REPO_LEVEL_CONFIG_FILE = ".githubCrawler"
         const val APPLICATION_JSON = "application/json"
@@ -56,15 +54,15 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
     }
 
     private val internalGitHubClient: InternalGitHubClient = Feign.builder()
-            .client(ApacheHttpClient())
-            .encoder(GsonEncoder())
-            .decoder(GitHubResponseDecoder())
-            .errorDecoder(GiHubErrorDecoder())
-            .decode404()
-            .requestInterceptor(GitHubOauthTokenSetter(oauthToken))
-            .logger(Slf4jLogger(RemoteGitHubImpl::class.java))
-            .logLevel(Logger.Level.FULL)
-            .target<InternalGitHubClient>(InternalGitHubClient::class.java, gitHubUrl)
+        .client(ApacheHttpClient())
+        .encoder(GsonEncoder())
+        .decoder(GitHubResponseDecoder())
+        .errorDecoder(GiHubErrorDecoder())
+        .decode404()
+        .requestInterceptor(GitHubOauthTokenSetter(oauthToken))
+        .logger(Slf4jLogger(RemoteGitHubImpl::class.java))
+        .logLevel(Logger.Level.FULL)
+        .target<InternalGitHubClient>(InternalGitHubClient::class.java, gitHubUrl)
 
     private val httpClient = OkHttpClient()
 
@@ -92,8 +90,8 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
         val reposUrl = "$gitHubUrl/" + userOrOrg() + "/$organizationName/repos"
 
         val requestBuilder = okhttp3.Request.Builder()
-                .url(reposUrl)
-                .header(ACCEPT, APPLICATION_GITHUB_MERCY_PREVIEW_JSON)
+            .url(reposUrl)
+            .header(ACCEPT, APPLICATION_GITHUB_MERCY_PREVIEW_JSON)
 
         addOAuthTokenIfRequired(requestBuilder)
 
@@ -148,8 +146,8 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
 
 
             val nextPageRequestBuilder = okhttp3.Request.Builder()
-                    .url(nextPageLink)
-                    .header(ACCEPT, APPLICATION_GITHUB_MERCY_PREVIEW_JSON)
+                .url(nextPageLink)
+                .header(ACCEPT, APPLICATION_GITHUB_MERCY_PREVIEW_JSON)
 
             addOAuthTokenIfRequired(nextPageRequestBuilder)
 
@@ -225,9 +223,11 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
 
         val searchCodeUrl = HttpUrl.parse(gitHubUrl +buildQueryString(query,repositoryFullName))!!.newBuilder().build().toString()
 
+        log.info("fetching code search result from $searchCodeUrl");
+
         val requestBuilder = okhttp3.Request.Builder()
-                .url(searchCodeUrl)
-                .header(ACCEPT, APPLICATION_JSON)
+            .url(searchCodeUrl)
+            .header(ACCEPT, APPLICATION_JSON)
 
         addOAuthTokenIfRequired(requestBuilder)
 
@@ -263,8 +263,8 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
         }
 
         val requestBuilder = okhttp3.Request.Builder()
-                .url(fileOnRepository.downloadUrl)
-                .header(ACCEPT, APPLICATION_JSON)
+            .url(fileOnRepository.downloadUrl)
+            .header(ACCEPT, APPLICATION_JSON)
 
         addOAuthTokenIfRequired(requestBuilder)
 
@@ -280,12 +280,12 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
     override fun fetchCommits(repositoryFullName: String, perPage: Int): Set<Commit> {
 
         return try {
-                internalGitHubClient.fetchCommits(repositoryFullName, perPage)
-            }
-            catch (e: GitHubResponseDecoder.GithubException) {
-                log.warn("not able to fetch commits for repo $repositoryFullName",e)
-                emptySet()
-            }
+            internalGitHubClient.fetchCommits(repositoryFullName, perPage)
+        }
+        catch (e: GitHubResponseDecoder.GithubException) {
+            log.warn("not able to fetch commits for repo $repositoryFullName",e)
+            emptySet()
+        }
 
     }
 
@@ -312,8 +312,8 @@ class RemoteGitHubImpl @JvmOverloads constructor(val gitHubUrl: String, val user
         }
 
         val requestBuilder = okhttp3.Request.Builder()
-                .url(configFileOnRepository.downloadUrl)
-                .header(ACCEPT, APPLICATION_JSON)
+            .url(configFileOnRepository.downloadUrl)
+            .header(ACCEPT, APPLICATION_JSON)
 
         addOAuthTokenIfRequired(requestBuilder)
 
diff --git a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitLabImpl.kt b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitLabImpl.kt
index 1a27fe7..f6d19bd 100644
--- a/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitLabImpl.kt
+++ b/github-crawler-core/src/main/kotlin/com/societegenerale/githubcrawler/remote/RemoteGitLabImpl.kt
@@ -51,15 +51,15 @@ class RemoteGitLabImpl @JvmOverloads constructor(val gitLabUrl: String, val priv
     }
 
     private val internalGitLabClient: InternalGitLabClient = Feign.builder()
-            .client(ApacheHttpClient())
-            .encoder(GsonEncoder())
-            .decoder(GitLabResponseDecoder())
-            .errorDecoder(GiLabErrorDecoder())
-            .decode404()
-            .requestInterceptor(GitLabPrivateTokenSetter(privateToken))
-            .logger(Slf4jLogger(RemoteGitLabImpl::class.java))
-            .logLevel(Logger.Level.FULL)
-            .target<InternalGitLabClient>(InternalGitLabClient::class.java, gitLabUrl)
+        .client(ApacheHttpClient())
+        .encoder(GsonEncoder())
+        .decoder(GitLabResponseDecoder())
+        .errorDecoder(GiLabErrorDecoder())
+        .decode404()
+        .requestInterceptor(GitLabPrivateTokenSetter(privateToken))
+        .logger(Slf4jLogger(RemoteGitLabImpl::class.java))
+        .logLevel(Logger.Level.FULL)
+        .target<InternalGitLabClient>(InternalGitLabClient::class.java, gitLabUrl)
 
     private val httpClient = OkHttpClient()
 
@@ -98,9 +98,9 @@ class RemoteGitLabImpl @JvmOverloads constructor(val gitLabUrl: String, val priv
         val repoSearchUrl = gitLabUrl +"/projects/"+repoNameToIdMapping.get(repositoryFullName)+"/search?scope=blobs&"+searchString
 
         val request = okhttp3.Request.Builder()
-                .url(repoSearchUrl)
-                .header(PRIVATE_TOKEN_HEADER_KEY, privateToken)
-                .build()
+            .url(repoSearchUrl)
+            .header(PRIVATE_TOKEN_HEADER_KEY, privateToken)
+            .build()
 
         val httpResponse=httpClient.newCall(request).execute()
 
@@ -161,23 +161,23 @@ class RemoteGitLabImpl @JvmOverloads constructor(val gitLabUrl: String, val priv
      */
     override fun fetchFileContent(repositoryFullName: String, branchName: String, fileToFetch: String): String {
 
-            val fetchFileUrl = gitLabUrl +"/projects/"+repoNameToIdMapping.get(repositoryFullName)+"/repository/files/"+fileToFetch+"/raw?ref="+branchName
+        val fetchFileUrl = gitLabUrl +"/projects/"+repoNameToIdMapping.get(repositoryFullName)+"/repository/files/"+fileToFetch+"/raw?ref="+branchName
 
-            val request = okhttp3.Request.Builder()
-                    .url(fetchFileUrl)
-                    .header(PRIVATE_TOKEN_HEADER_KEY, privateToken)
-                    .build()
+        val request = okhttp3.Request.Builder()
+            .url(fetchFileUrl)
+            .header(PRIVATE_TOKEN_HEADER_KEY, privateToken)
+            .build()
 
-            val response = httpClient.newCall(request).execute()
+        val response = httpClient.newCall(request).execute()
 
-            if(response.code()==HttpStatus.NOT_FOUND.value()){
-                throw NoFileFoundException("can't find $fileToFetch in repo $repositoryFullName, in branch $branchName")
-            }
-            else if (!response.isSuccessful) {
-                throw NoReachableRepositories("GET call to ${fetchFileUrl} wasn't successful. Code : ${response.code()}, Message : ${response.message()}")
-            }
+        if(response.code()==HttpStatus.NOT_FOUND.value()){
+            throw NoFileFoundException("can't find $fileToFetch in repo $repositoryFullName, in branch $branchName")
+        }
+        else if (!response.isSuccessful) {
+            throw NoReachableRepositories("GET call to ${fetchFileUrl} wasn't successful. Code : ${response.code()}, Message : ${response.message()}")
+        }
 
-            return response.body()!!.string()
+        return response.body()!!.string()
     }
 
     @Throws(NoReachableRepositories::class)
@@ -247,12 +247,12 @@ data class GitLabRepository (val id : Int,val web_url : String, val path : Strin
     fun toRepository(): Repository {
 
         return  Repository(url = web_url,
-                           fullName = path_with_namespace,
-                           name = path,
-                           defaultBranch = default_branch,
-                           creationDate = created_at,
-                           lastUpdateDate = last_activity_at,
-                           topics = tag_list)
+            fullName = path_with_namespace,
+            name = path,
+            defaultBranch = default_branch,
+            creationDate = created_at,
+            lastUpdateDate = last_activity_at,
+            topics = tag_list)
 
     }
 }
diff --git a/github-crawler-core/src/main/resources/application.yml b/github-crawler-core/src/test/resources/application.yml
similarity index 100%
rename from github-crawler-core/src/main/resources/application.yml
rename to github-crawler-core/src/test/resources/application.yml
diff --git a/pom.xml b/pom.xml
index 90d7d71..e6cbd82 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,10 +60,14 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>11</java.version>
 
-        <kotlin.version>1.6.10</kotlin.version>
-        <gson.version>2.8.5</gson.version>
 
+        <kotlin.version>1.6.10</kotlin.version>
+        <gson.version>2.9.0</gson.version>
         <jacoco.version>0.8.7</jacoco.version>
+        <!-- some issues parsing Yaml files with Jackson 2.13 that Spring brings, so sticking it to latest 2.12-->
+        <!-- //TODO make it work with 2.13-->
+        <jackson-bom.version>2.12.6</jackson-bom.version>
+
     </properties>
 
 
@@ -211,7 +215,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
+                <version>3.10.1</version>
                 <executions>
                     <!-- Replacing default-compile as it is treated specially by maven -->
                     <execution>


Test patch:
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/AzureDevopsCrawlerIT.java b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/AzureDevopsCrawlerIT.java
new file mode 100644
index 0000000..8f7c169
--- /dev/null
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/AzureDevopsCrawlerIT.java
@@ -0,0 +1,115 @@
+package com.societegenerale.githubcrawler;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.societegenerale.githubcrawler.config.GitHubCrawlerAutoConfiguration;
+import com.societegenerale.githubcrawler.config.TestConfig;
+import com.societegenerale.githubcrawler.remote.RemoteGitHub;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.util.ResourceUtils;
+
+
+@SpringBootTest(classes = {TestConfig.class, GitHubCrawlerAutoConfiguration.class})
+@ActiveProfiles(profiles = {"azureDevopsTest"})
+@DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
+class AzureDevopsCrawlerIT {
+
+  Logger log = LoggerFactory.getLogger(this.getClass().toString());
+
+  private final static String API_VERSION = "api-version=7.1-preview.1";
+
+  private final static int AZUREDEVOPS_SERVER_PORT_FOR_TESTS=9901;
+
+  @Autowired
+  ApplicationContext context;
+
+  @Autowired
+  private GitHubCrawler crawler;
+
+  private final WireMockServer wm = new WireMockServer(
+      options()
+          .port(AZUREDEVOPS_SERVER_PORT_FOR_TESTS)
+          .usingFilesUnderDirectory("src/test/resources/azureDevops"));
+
+  @BeforeEach
+  void mockSetUp() throws IOException {
+
+    wm.start();
+    configureFor(AZUREDEVOPS_SERVER_PORT_FOR_TESTS);
+
+    stubFor(WireMock.get(urlEqualTo("/platform/platform-projects/_apis/git/repositories?"+API_VERSION))
+        .willReturn(aResponse()
+            .withBodyFile("repositories.json")
+            .withStatus(200)));
+
+    stubFor(WireMock.get(urlEqualTo("/platform/platform-projects/_apis/git/repositories/my-helm-chart/items?path=.azureDevopsCrawler&"+API_VERSION))
+        .willReturn(aResponse().withStatus(404)));
+
+    stubFor(WireMock.get(urlEqualTo("/platform/platform-projects/_apis/git/repositories/vendor-portal-ui/items?path=.azureDevopsCrawler&"+API_VERSION))
+        .willReturn(aResponse().withStatus(404)));
+
+    stubFor(WireMock.get(urlEqualTo("/platform/platform-projects/_apis/git/repositories/my-helm-chart/items?path=pom.xml&versionDescriptor.versionType=branch&versionDescriptor.version=main&"+API_VERSION))
+        .willReturn((aResponse()
+            .withBody(FileUtils.readFileToString(ResourceUtils.getFile("classpath:sample_pom.xml"), "UTF-8"))
+            .withStatus(200))));
+
+    stubFor(WireMock.get(urlEqualTo("/platform/platform-projects/_apis/git/repositories/vendor-portal-ui/items?path=pom.xml&versionDescriptor.versionType=branch&versionDescriptor.version=main&"+API_VERSION))
+        .willReturn(aResponse().withStatus(404)));
+  }
+
+
+  @Test
+  void shouldCrawl() throws IOException {
+
+    var crawlers=context.getBeanNamesForType(GitHubCrawler.class);
+    log.info("found "+crawlers.length+" crawlers in context : "+crawlers[0]);
+
+    var crawlerFromContext= context.getBean(GitHubCrawler.class);
+    log.info("crawlerId : "+crawlerFromContext);
+    log.info("properties : "+crawlerFromContext.getGitHubCrawlerProperties());
+
+
+    var remoteGitHubs=context.getBeanNamesForType(RemoteGitHub.class);
+    log.info("found "+remoteGitHubs.length+" remoteGitHubs in context : "+remoteGitHubs[0]);
+    var remoteGitHubFromContext= context.getBean(RemoteGitHub.class);
+    log.info("remoteGitHubFromContextId : "+remoteGitHubFromContext);
+
+
+
+    log.info("about to start crawling...");
+
+    log.info("URL config used : "+crawler.getGitHubCrawlerProperties().getGithubConfig().getApiUrl());
+
+    log.info("nb misc tasks : "+crawler.getGitHubCrawlerProperties().getMiscRepositoryTasks().size());
+
+
+    try {
+      crawler.crawl();
+    } catch (IOException e) {
+      log.error("problem while crawling",e);
+      throw e;
+    }
+
+
+    //TODO have an in-memory output and assert content
+  }
+
+
+}
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerIT.java b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerIT.java
index 8148047..ff3840e 100644
--- a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerIT.java
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerIT.java
@@ -26,13 +26,16 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
 
 @ExtendWith(SpringExtension.class)
 @SpringBootTest(classes = {TestConfig.class, GitHubCrawlerAutoConfiguration.class})
-@ActiveProfiles(profiles = {"test", "profilesAreAWayOfGrouping"})
+@ActiveProfiles(profiles = {"gitHubTest", "profilesAreAWayOfGrouping"})
+@DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
 public class GitHubCrawlerIT {
 
   private final int MAX_TIMEOUT_FOR_CRAWLER = 10;
@@ -75,11 +78,17 @@ public class GitHubCrawlerIT {
       hasGitHubMockServerStarted = true;
     }
 
-    output.reset();
     githubMockServer.reset();
+  }
+
+  @BeforeEach
+  void resetSharedData() {
+    output.reset();
+
     crawler.getGitHubCrawlerProperties().setRepositoriesToExclude(new ArrayList<>());
     crawler.getGitHubCrawlerProperties().setPublishExcludedRepositories(true);
     crawler.getGitHubCrawlerProperties().setCrawlAllBranches(false);
+    crawler.getTasksToPerform().clear();
   }
 
 
@@ -104,7 +113,7 @@ public class GitHubCrawlerIT {
 
     assertThat(githubMockServer.isHasCalledNextPage()).as("next page wasn't called").isTrue();
 
-    assertThat(githubMockServer.getRepoConfigHits().size()).isGreaterThanOrEqualTo(nbRepositoriesInOrga);
+    assertThat(githubMockServer.getRepoConfigHits()).hasSizeGreaterThanOrEqualTo(nbRepositoriesInOrga);
     assertThat(githubMockServer.getNbPages()).isEqualTo(2);
 
   }
@@ -113,7 +122,7 @@ public class GitHubCrawlerIT {
   void excludingRepositoriesOnServerConfigSideWithSingleRegexp() throws IOException {
 
     String excludedRepoName = "api-.*";
-    crawler.getGitHubCrawlerProperties().setRepositoriesToExclude(Arrays.asList(excludedRepoName));
+    crawler.getGitHubCrawlerProperties().setRepositoriesToExclude(List.of(excludedRepoName));
     crawler.crawl();
 
     assertOnlyThisRepoIsFlaggedAsExcluded("api-gateway");
@@ -162,7 +171,7 @@ public class GitHubCrawlerIT {
         .until(() -> assertThat(processedRepositories).hasSize(nbRepositoriesInOrga));
 
     processedRepositories.stream().forEach(repo -> {
-      assertThat(repo.getGroups()).containsExactlyInAnyOrder("test", "profilesAreAWayOfGrouping");
+      assertThat(repo.getGroups()).containsExactlyInAnyOrder("gitHubTest", "profilesAreAWayOfGrouping");
     });
 
   }
@@ -175,8 +184,9 @@ public class GitHubCrawlerIT {
 
     crawler.crawl();
 
-    assertThat(githubMockServer.getPomXmlHits()).hasSize(nbRepositoriesInOrga - 1);
-    assertThat(githubMockServer.getPomXmlHits()).doesNotContain(excludedRepoName);
+    assertThat(githubMockServer.getPomXmlHits())
+        .hasSize(nbRepositoriesInOrga - 1)
+        .doesNotContain(excludedRepoName);
   }
 
   @Test
@@ -305,6 +315,10 @@ public class GitHubCrawlerIT {
   @Test
   void shouldPerformSearchOnAllRepos() throws IOException {
 
+    assertThat(output.getAnalyzedRepositories()).isEmpty();
+    assertThat(githubMockServer.getSearchHitsCount()).isEqualTo(0);
+
+
     crawler.crawl();
 
     Collection<Repository> actualRepositories = output.getAnalyzedRepositories().values();
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitHubImplTest.kt b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitHubImplTest.kt
index b9e85fc..381c916 100644
--- a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitHubImplTest.kt
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitHubImplTest.kt
@@ -9,13 +9,15 @@ import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.test.annotation.DirtiesContext
 
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.junit.jupiter.SpringExtension
 import java.util.concurrent.TimeUnit.SECONDS
 
 @ExtendWith(SpringExtension::class)
-@ActiveProfiles(profiles = arrayOf("test"))
+@ActiveProfiles(profiles = arrayOf("gitHubTest"))
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class RemoteGitHubImplTest {
 
     companion object {
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitLabImplTest.kt b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitLabImplTest.kt
index 6f6f725..4474b04 100644
--- a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitLabImplTest.kt
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/RemoteGitLabImplTest.kt
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.test.annotation.DirtiesContext
 
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.junit.jupiter.SpringExtension
@@ -17,7 +18,8 @@ import java.util.concurrent.TimeUnit.SECONDS
 
 
 @ExtendWith(SpringExtension::class)
-@ActiveProfiles(profiles = arrayOf("test"))
+@ActiveProfiles(profiles = arrayOf("gitLabTest"))
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 class RemoteGitLabImplTest {
 
     companion object {
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitHubMock.java b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitHubMock.java
index 58b115f..2bf4239 100644
--- a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitHubMock.java
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitHubMock.java
@@ -1,5 +1,16 @@
 package com.societegenerale.githubcrawler.mocks;
 
+import static com.societegenerale.githubcrawler.remote.RemoteGitHubImpl.CONFIG_VALIDATION_REQUEST_HEADER;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import net.codestory.http.Context;
 import net.codestory.http.WebServer;
 import net.codestory.http.constants.HttpStatus;
@@ -15,20 +26,12 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.ResourceUtils;
 import org.springframework.util.StreamUtils;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.util.*;
-
-import static com.societegenerale.githubcrawler.remote.RemoteGitHubImpl.CONFIG_VALIDATION_REQUEST_HEADER;
-
 @Component
 public class GitHubMock implements RemoteServiceMock {
 
     private static final Logger log = org.slf4j.LoggerFactory.getLogger(GitHubMock.class);
 
-    private String organisationResponse = "organisation.json";
+    private String organisationResponse = "github/organisation.json";
 
     public final static String REPO_EXCLUDED_CONFIG = "excluded: true";
 
@@ -158,10 +161,10 @@ public class GitHubMock implements RemoteServiceMock {
 
         if (aSubDirectory != null) {
 
-            fileOnRepoTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:template_FileOnRepo_withSubDir.json"), "UTF-8");
+            fileOnRepoTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:github/template_FileOnRepo_withSubDir.json"), "UTF-8");
             fileOnRepoTemplate = fileOnRepoTemplate.replaceFirst("\\$\\{SUB_DIRECTORY}", aSubDirectory);
         } else {
-            fileOnRepoTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:template_FileOnRepo.json"), "UTF-8");
+            fileOnRepoTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:github/template_FileOnRepo.json"), "UTF-8");
         }
 
         fileOnRepoTemplate = fileOnRepoTemplate.replaceFirst("\\$\\{REPO}", repo);
@@ -180,7 +183,7 @@ public class GitHubMock implements RemoteServiceMock {
 
         if (reposWithPomXml.contains(repo)) {
 
-            String pomXMlTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:pomXmlFileOnRepo.json"), "UTF-8");
+            String pomXMlTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:github/pomXmlFileOnRepo.json"), "UTF-8");
 
             return new Payload("application/json", pomXMlTemplate.replaceFirst("\\$\\{REPO}", repo));
         } else {
@@ -200,7 +203,7 @@ public class GitHubMock implements RemoteServiceMock {
         if (repoConfigPerRepo.containsKey(repo)) {
             log.info("\t returning something..");
 
-            String repoConfigTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:dummyFileOnRepo.json"), "UTF-8");
+            String repoConfigTemplate = FileUtils.readFileToString(ResourceUtils.getFile("classpath:github/dummyFileOnRepo.json"), "UTF-8");
 
             return new Payload("application/json", repoConfigTemplate.replaceFirst("\\$\\{REPO}", repo));
 
@@ -247,7 +250,7 @@ public class GitHubMock implements RemoteServiceMock {
 
         searchHitsCount++;
 
-        return new Payload("application/json", FileUtils.readFileToString(ResourceUtils.getFile("classpath:searchResult.json"), "UTF-8"));
+        return new Payload("application/json", FileUtils.readFileToString(ResourceUtils.getFile("classpath:github/searchResult.json"), "UTF-8"));
 
     }
 
@@ -316,7 +319,7 @@ public class GitHubMock implements RemoteServiceMock {
 
         log.debug("received a branches request for repo {}..", repoName);
 
-        InputStream is = getClass().getClassLoader().getResourceAsStream("branches.json");
+        InputStream is = getClass().getClassLoader().getResourceAsStream("github/branches.json");
         String jsonString = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
 
         return new Payload("application/json", jsonString);
diff --git a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitLabMock.java b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitLabMock.java
index 38b0a96..43b08aa 100644
--- a/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitLabMock.java
+++ b/github-crawler-autoconfigure/src/test/java/com/societegenerale/githubcrawler/mocks/GitLabMock.java
@@ -1,5 +1,8 @@
 package com.societegenerale.githubcrawler.mocks;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import net.codestory.http.WebServer;
 import net.codestory.http.payload.Payload;
 import org.apache.commons.io.FileUtils;
@@ -7,10 +10,6 @@ import org.slf4j.Logger;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ResourceUtils;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
 @Component
 public class GitLabMock implements RemoteServiceMock {
 
@@ -57,7 +56,7 @@ public class GitLabMock implements RemoteServiceMock {
 
         log.info("performing a search for repoId {} with query string {}",repoId,searchedString);
 
-        String searchResult = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitLabRepoSearchResults.json"), "UTF-8");
+        String searchResult = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitlab/gitLabRepoSearchResults.json"), "UTF-8");
 
         return new Payload("application/json", searchResult);
 
@@ -79,7 +78,7 @@ public class GitLabMock implements RemoteServiceMock {
 
         log.info("fetching groups for name {}",byGroupName);
 
-        String groupListWIthOneElem = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitLabGroups.json"), "UTF-8");
+        String groupListWIthOneElem = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitlab/gitLabGroups.json"), "UTF-8");
 
         return new Payload("application/json", groupListWIthOneElem);
 
@@ -89,7 +88,7 @@ public class GitLabMock implements RemoteServiceMock {
 
         log.info("get repositories for group {}",groupId);
 
-        String repositoriesForGroup = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitLabRepositories.json"), "UTF-8");
+        String repositoriesForGroup = FileUtils.readFileToString(ResourceUtils.getFile("classpath:gitlab/gitLabRepositories.json"), "UTF-8");
 
         return new Payload("application/json", repositoriesForGroup);
     }
diff --git a/github-crawler-autoconfigure/src/test/resources/application-azureDevopsTest.yml b/github-crawler-autoconfigure/src/test/resources/application-azureDevopsTest.yml
new file mode 100644
index 0000000..ff3e321
--- /dev/null
+++ b/github-crawler-autoconfigure/src/test/resources/application-azureDevopsTest.yml
@@ -0,0 +1,21 @@
+github-crawler:
+
+  githubConfig:
+    type: "AZURE_DEVOPS"
+    apiUrl: http://localhost:9901/
+    organizationName: "platform#platform-projects"
+
+  publishExcludedRepositories: false
+
+  crawlAllBranches: false
+
+  indicatorsToFetchByFile:
+    "[pom.xml]":
+        - name: spring_boot_starter_parent_version
+          type: findDependencyVersionInXml
+          params:
+            artifactId: spring-boot-starter-parent
+
+
+
+
diff --git a/github-crawler-autoconfigure/src/test/resources/application-test.yml b/github-crawler-autoconfigure/src/test/resources/application-gitHubTest.yml
similarity index 98%
rename from github-crawler-autoconfigure/src/test/resources/application-test.yml
rename to github-crawler-autoconfigure/src/test/resources/application-gitHubTest.yml
index 88e6b6f..7f780fc 100644
--- a/github-crawler-autoconfigure/src/test/resources/application-test.yml
+++ b/github-crawler-autoconfigure/src/test/resources/application-gitHubTest.yml
@@ -1,6 +1,7 @@
 github-crawler:
 
   githubConfig:
+    type: "GITHUB"
     apiUrl: http://localhost:9900/api/v3
     organizationName: MyOrganization
 
diff --git a/github-crawler-autoconfigure/src/test/resources/application-gitLabTest.yml b/github-crawler-autoconfigure/src/test/resources/application-gitLabTest.yml
new file mode 100644
index 0000000..a0a4415
--- /dev/null
+++ b/github-crawler-autoconfigure/src/test/resources/application-gitLabTest.yml
@@ -0,0 +1,37 @@
+github-crawler:
+
+  githubConfig:
+    type: "GITLAB"
+    apiUrl: http://localhost:9900/api/v3
+    organizationName: MyOrganization
+
+  publishExcludedRepositories: false
+
+  crawlAllBranches: false
+
+  indicatorsToFetchByFile:
+    "[pom.xml]":
+        - name: spring_boot_starter_parent_version
+          type: findDependencyVersionInXml
+          params:
+            artifactId: spring-boot-starter-parent
+    Jenkinsfile:
+        - name: build_helper_package
+          type: findFirstValueWithRegexpCapture
+          params:
+            pattern: "(?s).*com\\.a\\.given\\.package\\BuildHelpers.*"
+    Dockerfile:
+        - name: docker_image_used
+          type: findFirstValueWithRegexpCapture
+          params:
+            pattern: "FROM\\s.*\\/(.*)\\s?.*"
+
+  misc-repository-tasks:
+      - name: "nbOfMetricsInPomXml"
+        type: "countHitsOnRepoSearch"
+        params:
+          queryString: "q=metrics+extension:xml"
+
+
+
+
diff --git a/github-crawler-autoconfigure/src/test/resources/azureDevops/__files/repositories.json b/github-crawler-autoconfigure/src/test/resources/azureDevops/__files/repositories.json
new file mode 100644
index 0000000..cff6292
--- /dev/null
+++ b/github-crawler-autoconfigure/src/test/resources/azureDevops/__files/repositories.json
@@ -0,0 +1,47 @@
+{
+  "value": [
+    {
+      "id": "b2222b21-8680-4f78-8255-0167a40bf5a8",
+      "name": "my-helm-chart",
+      "url": "https://dev.azure.com/my-platform/111111c0-ff82-4e2c-8b71-fbca48f259eb/_apis/git/repositories/b2222b21-8680-4f78-8255-0167a40bf5a8",
+      "project": {
+        "id": "111111c0-ff82-4e2c-8b71-fbca48f259eb",
+        "name": "my-platform-projects",
+        "description": "My Projects",
+        "url": "https://dev.azure.com/my-platform/_apis/projects/111111c0-ff82-4e2c-8b71-fbca48f259eb",
+        "state": "wellFormed",
+        "revision": 34,
+        "visibility": "private",
+        "lastUpdateTime": "2021-09-16T05:04:19.307Z"
+      },
+      "defaultBranch": "refs/heads/main",
+      "size": 79584,
+      "remoteUrl": "https://my-platform@dev.azure.com/my-platform/my-platform-projects/_git/my-helm-chart",
+      "sshUrl": "git@ssh.dev.azure.com:v3/my-platform/my-platform-projects/my-helm-chart",
+      "webUrl": "https://dev.azure.com/my-platform/my-platform-projects/_git/my-helm-chart",
+      "isDisabled": false
+    },
+    {
+      "id": "b3333e7f-9e5b-47cb-a6b3-01788b6bde25",
+      "name": "vendor-portal-ui",
+      "url": "https://dev.azure.com/my-platform/111111c0-ff82-4e2c-8b71-fbca48f259eb/_apis/git/repositories/b3333e7f-9e5b-47cb-a6b3-01788b6bde25",
+      "project": {
+        "id": "111111c0-ff82-4e2c-8b71-fbca48f259eb",
+        "name": "my-platform-projects",
+        "description": "Projects developed by us",
+        "url": "https://dev.azure.com/my-platform/_apis/projects/111111c0-ff82-4e2c-8b71-fbca48f259eb",
+        "state": "wellFormed",
+        "revision": 34,
+        "visibility": "private",
+        "lastUpdateTime": "2021-09-16T05:04:19.307Z"
+      },
+      "defaultBranch": "refs/heads/main",
+      "size": 766571,
+      "remoteUrl": "https://my-platform@dev.azure.com/my-platform/my-platform-projects/_git/vendor-portal-ui",
+      "sshUrl": "git@ssh.dev.azure.com:v3/my-platform/my-platform-projects/vendor-portal-ui",
+      "webUrl": "https://dev.azure.com/my-platform/my-platform-projects/_git/vendor-portal-ui",
+      "isDisabled": false
+    }
+
+  ]
+}
\ No newline at end of file
diff --git a/github-crawler-core/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerTest.kt b/github-crawler-core/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerTest.kt
index e2a1cef..c2f01ec 100644
--- a/github-crawler-core/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerTest.kt
+++ b/github-crawler-core/src/test/java/com/societegenerale/githubcrawler/GitHubCrawlerTest.kt
@@ -24,12 +24,14 @@ class GitHubCrawlerTest {
 
     private val output = InMemoryGitHubCrawlerOutput()
     private val outputs: List<GitHubCrawlerOutput> = arrayListOf(output)
-    private val repositoryEnricher = RepositoryEnricher(mockRemoteGitHub)
+
 
     private val fileToParse="pom.xml"
     private val indicator=IndicatorDefinition("indicName","findFilePath")
 
-    private val fileContentParsers=listOf(SimpleFilePathParser())
+    private val availableParsersAndTasks = AvailableParsersAndTasks(listOf(SimpleFilePathParser()), emptyList())
+
+    private val repositoryEnricher = RepositoryEnricher(mockRemoteGitHub,availableParsersAndTasks)
 
     private val gitHubCrawlerProperties = GitHubCrawlerProperties(githubConfig= GithubConfig(), indicatorsToFetchByFile = mapOf(Pair(FileToParse(fileToParse, null), listOf(indicator))))
     private val mockEnvironment = mock(Environment::class.java)
@@ -41,7 +43,7 @@ class GitHubCrawlerTest {
     @BeforeEach
     fun setUp() {
 
-        gitHubCrawler = GitHubCrawler(mockRemoteGitHub, outputs, repositoryEnricher, gitHubCrawlerProperties, mockEnvironment, organizationName, mockConfigValidator,fileContentParsers)
+        gitHubCrawler = GitHubCrawler(mockRemoteGitHub, outputs, repositoryEnricher, gitHubCrawlerProperties, mockEnvironment, organizationName, mockConfigValidator,availableParsersAndTasks)
 
         `when`(mockRemoteGitHub.fetchRepositories(organizationName)).thenReturn(setOf(
                 Repository(url = "url1", fullName = "fullRepo1", name = "repo1", defaultBranch = "master", creationDate = Date(), lastUpdateDate = Date(), topics = listOf("topic1a", "topic1b")),