diff --git a/.gitignore b/.gitignore
index 5085114..18c32db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -177,6 +177,7 @@ launchSettings.json
UpgradeLog.htm
*.*.ini
*.*.json
+src/Documentation/python
# Public repo ignores
.github/pull_request_template.md
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 198a9b8..d641ca5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
enable
true
true
- 4.3.0
+ 4.3.1
Salesforce, Inc.
Salesforce, Inc.
Copyright (c) 2024, Salesforce, Inc. and its licensors
diff --git a/src/Tableau.Migration/Api/IContentReferenceFinderFactoryExtensions.cs b/src/Tableau.Migration/Api/IContentReferenceFinderFactoryExtensions.cs
index 5d3be6c..bd82b89 100644
--- a/src/Tableau.Migration/Api/IContentReferenceFinderFactoryExtensions.cs
+++ b/src/Tableau.Migration/Api/IContentReferenceFinderFactoryExtensions.cs
@@ -23,7 +23,6 @@
using Tableau.Migration.Api.Rest;
using Tableau.Migration.Api.Rest.Models;
using Tableau.Migration.Content;
-using Tableau.Migration.Content.Schedules;
using Tableau.Migration.Content.Search;
using Tableau.Migration.Resources;
@@ -126,19 +125,6 @@ internal static class IContentReferenceFinderFactoryExtensions
cancel)
.ConfigureAwait(false);
- public static async Task FindExtractRefreshContentAsync(
- this IContentReferenceFinderFactory finderFactory,
- ExtractRefreshContentType contentType,
- Guid contentId,
- CancellationToken cancel)
- {
- var finder = finderFactory.ForExtractRefreshContent(contentType);
-
- var content = await finder.FindByIdAsync(contentId, cancel).ConfigureAwait(false);
-
- return Guard.AgainstNull(content, nameof(content));
- }
-
private static async Task FindAsync(
this IContentReferenceFinderFactory finderFactory,
[NotNull] TResponse? response,
diff --git a/src/Tableau.Migration/Api/TasksApiClient.cs b/src/Tableau.Migration/Api/TasksApiClient.cs
index 2b7f6e8..74ca31a 100644
--- a/src/Tableau.Migration/Api/TasksApiClient.cs
+++ b/src/Tableau.Migration/Api/TasksApiClient.cs
@@ -32,6 +32,7 @@
using Tableau.Migration.Net.Rest;
using Tableau.Migration.Paging;
using Tableau.Migration.Resources;
+
using CloudResponses = Tableau.Migration.Api.Rest.Models.Responses.Cloud;
using ServerResponses = Tableau.Migration.Api.Rest.Models.Responses.Server;
@@ -70,17 +71,11 @@ public TasksApiClient(
///
public IServerTasksApiClient ForServer()
- => ExecuteForInstanceType(
- TableauInstanceType.Server,
- _sessionProvider.InstanceType,
- () => this);
+ => ExecuteForInstanceType(TableauInstanceType.Server, _sessionProvider.InstanceType, () => this);
///
public ICloudTasksApiClient ForCloud()
- => ExecuteForInstanceType(
- TableauInstanceType.Cloud,
- _sessionProvider.InstanceType,
- () => this);
+ => ExecuteForInstanceType(TableauInstanceType.Cloud, _sessionProvider.InstanceType, () => this);
#endregion
@@ -105,10 +100,7 @@ public async Task DeleteExtractRefreshTaskAsync(
async Task>> ICloudTasksApiClient.GetAllExtractRefreshTasksAsync(
CancellationToken cancel)
=> await GetAllExtractRefreshTasksAsync(
- (r, c) => CloudExtractRefreshTask.CreateManyAsync(
- r,
- ContentFinderFactory,
- c),
+ (r, c) => CloudExtractRefreshTask.CreateManyAsync(r, ContentFinderFactory, Logger, SharedResourcesLocalizer, c),
cancel)
.ConfigureAwait(false);
@@ -122,14 +114,18 @@ async Task> ICloudTasksApiClient.CreateExtract
.ForPostRequest()
.WithXmlContent(new CreateExtractRefreshTaskRequest(options))
.SendAsync(cancel)
- .ToResultAsync((r, c) =>
- CloudExtractRefreshTask.CreateAsync(
- r.Item,
- r.Schedule,
- ContentFinderFactory,
- c),
- SharedResourcesLocalizer,
- cancel)
+ .ToResultAsync(async (r, c) =>
+ {
+ var task = Guard.AgainstNull(r.Item, () => r.Item);
+ var finder = ContentFinderFactory.ForExtractRefreshContent(task.GetContentType());
+
+ var contentReference = await finder.FindByIdAsync(task.GetContentId(), cancel).ConfigureAwait(false);
+
+ // Since we published with a content reference, we expect the reference returned is valid/knowable.
+ Guard.AgainstNull(contentReference, () => contentReference);
+
+ return CloudExtractRefreshTask.Create(task, r.Schedule, contentReference);
+ }, SharedResourcesLocalizer, cancel)
.ConfigureAwait(false);
return result;
@@ -158,11 +154,7 @@ public async Task> PublishAsync(
///
async Task>> IServerTasksApiClient.GetAllExtractRefreshTasksAsync(CancellationToken cancel)
=> await GetAllExtractRefreshTasksAsync(
- (r, c) => ServerExtractRefreshTask.CreateManyAsync(
- r,
- ContentFinderFactory,
- _contentCacheFactory,
- c),
+ (r, c) => ServerExtractRefreshTask.CreateManyAsync(r, ContentFinderFactory, _contentCacheFactory, Logger, SharedResourcesLocalizer, c),
cancel)
.ConfigureAwait(false);
diff --git a/src/Tableau.Migration/Content/Schedules/Cloud/CloudExtractRefreshTask.cs b/src/Tableau.Migration/Content/Schedules/Cloud/CloudExtractRefreshTask.cs
index 79c944e..8b782bc 100644
--- a/src/Tableau.Migration/Content/Schedules/Cloud/CloudExtractRefreshTask.cs
+++ b/src/Tableau.Migration/Content/Schedules/Cloud/CloudExtractRefreshTask.cs
@@ -19,9 +19,11 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
using Tableau.Migration.Api.Rest.Models.Responses;
using Tableau.Migration.Api.Rest.Models.Responses.Cloud;
using Tableau.Migration.Content.Search;
+using Tableau.Migration.Resources;
namespace Tableau.Migration.Content.Schedules.Cloud
{
@@ -34,54 +36,33 @@ internal CloudExtractRefreshTask(
ExtractRefreshContentType contentType,
IContentReference contentReference,
ICloudSchedule schedule)
- : base(
- extractRefreshId,
- type,
- contentType,
- contentReference,
- schedule)
+ : base(extractRefreshId, type, contentType, contentReference, schedule)
{ }
public static async Task> CreateManyAsync(
- ExtractRefreshTasksResponse? response,
+ ExtractRefreshTasksResponse response,
IContentReferenceFinderFactory finderFactory,
+ ILogger logger, ISharedResourcesLocalizer localizer,
CancellationToken cancel)
=> await CreateManyAsync(
response,
response => response.Items.ExceptNulls(i => i.ExtractRefresh),
(r, c, cnl) => Task.FromResult(Create(r, r.Schedule, c)),
- finderFactory,
+ finderFactory, logger, localizer,
cancel)
.ConfigureAwait(false);
- public static async Task CreateAsync(
- ICloudExtractRefreshType? response,
- ICloudScheduleType? schedule,
- IContentReferenceFinderFactory finderFactory,
- CancellationToken cancel)
- => await CreateAsync(
- response,
- finderFactory,
- (r, c, cnl) => Task.FromResult(Create(r, schedule, c)),
- cancel)
- .ConfigureAwait(false);
-
- private static ICloudExtractRefreshTask Create(
- IExtractRefreshType? response,
- ICloudScheduleType? schedule,
+ public static ICloudExtractRefreshTask Create(
+ IExtractRefreshType response,
+ ICloudScheduleType schedule,
IContentReference content)
{
- Guard.AgainstNull(response, nameof(response));
-
return new CloudExtractRefreshTask(
response.Id,
response.Type!,
response.GetContentType(),
content,
- new CloudSchedule(
- Guard.AgainstNull(
- schedule,
- () => schedule)));
+ new CloudSchedule(schedule));
}
}
}
diff --git a/src/Tableau.Migration/Content/Schedules/ExtractRefreshTaskBase.cs b/src/Tableau.Migration/Content/Schedules/ExtractRefreshTaskBase.cs
index b57e74d..506cba8 100644
--- a/src/Tableau.Migration/Content/Schedules/ExtractRefreshTaskBase.cs
+++ b/src/Tableau.Migration/Content/Schedules/ExtractRefreshTaskBase.cs
@@ -20,10 +20,11 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
-using Tableau.Migration.Api;
+using Microsoft.Extensions.Logging;
using Tableau.Migration.Api.Rest.Models;
using Tableau.Migration.Api.Rest.Models.Responses;
using Tableau.Migration.Content.Search;
+using Tableau.Migration.Resources;
namespace Tableau.Migration.Content.Schedules
{
@@ -32,8 +33,11 @@ internal abstract class ExtractRefreshTaskBase :
where TSchedule : ISchedule
{
public string Type { get; set; }
+
public ExtractRefreshContentType ContentType { get; set; }
+
public IContentReference Content { get; set; }
+
public TSchedule Schedule { get; }
protected ExtractRefreshTaskBase(
@@ -54,57 +58,43 @@ protected ExtractRefreshTaskBase(
Schedule = schedule;
}
- protected static async Task CreateAsync(
- TExtractRefreshType? response,
- IContentReferenceFinderFactory finderFactory,
- Func> modelFactory,
- CancellationToken cancel)
- where TExtractRefreshType : class, IExtractRefreshType
- where TExtractRefreshTask: IExtractRefreshTask
- {
- Guard.AgainstNull(response, nameof(response));
-
- var contentReference = await finderFactory
- .FindExtractRefreshContentAsync(
- response.GetContentType(),
- response.GetContentId(),
- cancel)
- .ConfigureAwait(false);
-
- var model = await modelFactory(
- response,
- contentReference,
- cancel)
- .ConfigureAwait(false);
-
- return model;
- }
-
- protected static async Task> CreateManyAsync(
- TResponse? response,
+ protected static async Task> CreateManyAsync(
+ TResponse response,
Func> responseItemFactory,
Func> modelFactory,
IContentReferenceFinderFactory finderFactory,
+ ILogger logger, ISharedResourcesLocalizer localizer,
CancellationToken cancel)
where TResponse : ITableauServerResponse
where TExtractRefreshType : class, IExtractRefreshType
where TExtractRefreshTask: IExtractRefreshTask
{
- Guard.AgainstNull(response, nameof(response));
-
- var tasks = ImmutableArray.CreateBuilder();
-
- var items = responseItemFactory(response).ExceptNulls();
+ var items = responseItemFactory(response).ExceptNulls().ToImmutableArray();
+ var tasks = ImmutableArray.CreateBuilder(items.Length);
foreach (var item in items)
{
- tasks.Add(
- await CreateAsync(
- item,
- finderFactory,
- modelFactory,
- cancel)
- .ConfigureAwait(false));
+ var contentType = item.GetContentType();
+
+ if(contentType is ExtractRefreshContentType.Unknown)
+ {
+ logger.LogWarning(localizer[SharedResourceKeys.UnknownExtractRefreshContentTypeWarning], item.Id);
+ continue;
+ }
+
+ var finder = finderFactory.ForExtractRefreshContent(contentType);
+ var contentReference = await finder.FindByIdAsync(item.GetContentId(), cancel).ConfigureAwait(false);
+
+ /*
+ * Content reference is null when the referenced content item (e.g. workbook/data source)
+ * is in a private space or other "pre-manifest" filter.
+ *
+ * We similarly filter out those extract refresh tasks.
+ */
+ if(contentReference is not null)
+ {
+ tasks.Add(await modelFactory(item, contentReference, cancel).ConfigureAwait(false));
+ }
}
return tasks.ToImmutable();
diff --git a/src/Tableau.Migration/Content/Schedules/Server/ServerExtractRefreshTask.cs b/src/Tableau.Migration/Content/Schedules/Server/ServerExtractRefreshTask.cs
index 4ae212b..b1f5923 100644
--- a/src/Tableau.Migration/Content/Schedules/Server/ServerExtractRefreshTask.cs
+++ b/src/Tableau.Migration/Content/Schedules/Server/ServerExtractRefreshTask.cs
@@ -20,9 +20,11 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
using Tableau.Migration.Api.Rest.Models.Responses;
using Tableau.Migration.Api.Rest.Models.Responses.Server;
using Tableau.Migration.Content.Search;
+using Tableau.Migration.Resources;
namespace Tableau.Migration.Content.Schedules.Server
{
@@ -47,26 +49,25 @@ internal ServerExtractRefreshTask(
{ }
public static async Task> CreateManyAsync(
- ExtractRefreshTasksResponse? response,
+ ExtractRefreshTasksResponse response,
IContentReferenceFinderFactory finderFactory,
IContentCacheFactory contentCacheFactory,
+ ILogger logger, ISharedResourcesLocalizer localizer,
CancellationToken cancel)
=> await CreateManyAsync(
response,
response => response.Items.ExceptNulls(i => i.ExtractRefresh),
async (r, c, cnl) => await CreateAsync(r, c, contentCacheFactory, cnl).ConfigureAwait(false),
- finderFactory,
+ finderFactory, logger, localizer,
cancel)
.ConfigureAwait(false);
private static async Task CreateAsync(
- IServerExtractRefreshType? response,
+ IServerExtractRefreshType response,
IContentReference content,
IContentCacheFactory contentCacheFactory,
CancellationToken cancel)
{
- Guard.AgainstNull(response, nameof(response));
-
var scheduleCache = contentCacheFactory.ForContentType(true);
var schedule = await scheduleCache.ForIdAsync(response.Schedule.Id, cancel).ConfigureAwait(false);
diff --git a/src/Tableau.Migration/Resources/SharedResourceKeys.cs b/src/Tableau.Migration/Resources/SharedResourceKeys.cs
index c92eb87..941ea9c 100644
--- a/src/Tableau.Migration/Resources/SharedResourceKeys.cs
+++ b/src/Tableau.Migration/Resources/SharedResourceKeys.cs
@@ -142,5 +142,7 @@ internal static class SharedResourceKeys
public const string UserWithCustomViewDefaultSkippedMissingReferenceWarning = "UserWithCustomViewDefaultSkippedMissingReferenceWarning";
public const string DuplicateContentTypeConfigurationMessage = "DuplicateContentTypeConfigurationMessage";
+
+ public const string UnknownExtractRefreshContentTypeWarning = "UnknownExtractRefreshContentTypeWarning";
}
}
diff --git a/src/Tableau.Migration/Resources/SharedResources.resx b/src/Tableau.Migration/Resources/SharedResources.resx
index 9ce88d8..a0c186e 100644
--- a/src/Tableau.Migration/Resources/SharedResources.resx
+++ b/src/Tableau.Migration/Resources/SharedResources.resx
@@ -315,4 +315,7 @@ Owner with ID {OwnerID}: {owner}
Duplicate content type configuration found for content type {0}.
+
+ The extract refresh task with ID {TaskId} references an unknown content type. The task will be ignored.
+
\ No newline at end of file
diff --git a/tests/Tableau.Migration.Tests/Unit/Api/ApiTestBase.cs b/tests/Tableau.Migration.Tests/Unit/Api/ApiTestBase.cs
index cb06610..b435eb2 100644
--- a/tests/Tableau.Migration.Tests/Unit/Api/ApiTestBase.cs
+++ b/tests/Tableau.Migration.Tests/Unit/Api/ApiTestBase.cs
@@ -16,9 +16,7 @@
//
using System;
-using System.Collections.Generic;
using System.Net.Http;
-using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
@@ -27,11 +25,9 @@
using Tableau.Migration.Api.Publishing;
using Tableau.Migration.Api.Rest.Models;
using Tableau.Migration.Api.Rest.Models.Responses;
-using Tableau.Migration.Api.Rest.Models.Responses.Server;
using Tableau.Migration.Api.Tags;
using Tableau.Migration.Config;
using Tableau.Migration.Content;
-using Tableau.Migration.Content.Schedules;
using Tableau.Migration.Content.Schedules.Server;
using Tableau.Migration.Content.Search;
using Tableau.Migration.Net;
@@ -129,60 +125,5 @@ protected IHttpResponseMessage SetupSuccessResponse(HttpContent? content = null)
protected TService CreateService()
where TService : class
=> ActivatorUtilities.CreateInstance(Dependencies);
-
- protected void SetupExtractRefreshContentFinder(IExtractRefreshType extractRefresh)
- {
- var contentType = extractRefresh.GetContentType();
- var contentId = extractRefresh.GetContentId();
-
- var contentReference = Create>(m =>
- {
- m.SetupGet(r => r.Id).Returns(contentId);
- })
- .Object;
-
- switch (contentType)
- {
- case ExtractRefreshContentType.Workbook:
- SetupContentFinder(MockWorkbookFinder);
- break;
-
- case ExtractRefreshContentType.DataSource:
- SetupContentFinder(MockDataSourceFinder);
- break;
-
- default:
- throw new NotSupportedException($"Content type {contentType} is not supported.");
- }
-
- if (extractRefresh is IServerExtractRefreshType serverExtractRefresh)
- {
- Guard.AgainstNull(serverExtractRefresh.Schedule, () => serverExtractRefresh.Schedule);
-
- var scheduleId = serverExtractRefresh.Schedule.Id;
-
- var cachedSchedule = Create>(m =>
- {
- m.SetupGet(s => s.Id).Returns(scheduleId);
- })
- .Object;
-
- var scheduleReference = cachedSchedule.ToStub();
-
- MockScheduleFinder.Setup(f => f.FindByIdAsync(scheduleId, It.IsAny())).ReturnsAsync(scheduleReference);
-
- MockScheduleCache.Setup(f => f.ForIdAsync(scheduleId, It.IsAny())).ReturnsAsync(cachedSchedule);
- }
-
- void SetupContentFinder(Mock> mockFinder)
- where T : IContentReference
- => mockFinder.Setup(f => f.FindByIdAsync(contentId, It.IsAny())).ReturnsAsync(contentReference);
- }
-
- protected void SetupExtractRefreshContentFinder(IEnumerable extractRefreshes)
- {
- foreach (var extractRefresh in extractRefreshes)
- SetupExtractRefreshContentFinder(extractRefresh);
- }
}
}
diff --git a/tests/Tableau.Migration.Tests/Unit/Api/TasksApiClientTests.cs b/tests/Tableau.Migration.Tests/Unit/Api/TasksApiClientTests.cs
index fd30095..5a26be6 100644
--- a/tests/Tableau.Migration.Tests/Unit/Api/TasksApiClientTests.cs
+++ b/tests/Tableau.Migration.Tests/Unit/Api/TasksApiClientTests.cs
@@ -30,13 +30,15 @@
using Tableau.Migration.Content.Schedules;
using Tableau.Migration.Content.Schedules.Cloud;
using Tableau.Migration.Content.Schedules.Server;
+using Tableau.Migration.Tests.Unit.Content.Schedules;
using Xunit;
+
using CloudResponses = Tableau.Migration.Api.Rest.Models.Responses.Cloud;
using ServerResponses = Tableau.Migration.Api.Rest.Models.Responses.Server;
namespace Tableau.Migration.Tests.Unit.Api
{
- public class TasksApiClientTests
+ public sealed class TasksApiClientTests
{
public abstract class TasksApiClientTest : ApiClientTestBase
{
@@ -44,9 +46,13 @@ public abstract class TasksApiClientTest : ApiClientTestBase
protected TableauInstanceType CurrentInstanceType { get; set; }
+ protected ExtractRefreshTestCaches ExtractRefreshTestCaches { get; }
+
public TasksApiClientTest()
{
MockSessionProvider.SetupGet(p => p.InstanceType).Returns(() => CurrentInstanceType);
+
+ ExtractRefreshTestCaches = new(AutoFixture, MockDataSourceFinder, MockWorkbookFinder, MockScheduleFinder, MockScheduleCache);
}
protected static List AssertSuccess(IResult> result)
@@ -204,7 +210,7 @@ public async Task Gets_datasource_extract_refreshes()
var response = CreateCloudResponse(ExtractRefreshContentType.DataSource);
- SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
SetupSuccessResponse(response);
@@ -224,7 +230,7 @@ public async Task Gets_workbook_extract_refreshes()
var response = CreateCloudResponse(ExtractRefreshContentType.Workbook);
- SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
SetupSuccessResponse(response);
@@ -236,6 +242,25 @@ public async Task Gets_workbook_extract_refreshes()
Assert.Equal(expectedExtractRefreshes.Count, actualExtractRefreshes.Count);
Assert.DoesNotContain(actualExtractRefreshes, item => item.ContentType == ExtractRefreshContentType.DataSource);
}
+
+ [Fact]
+ public async Task Ignores_personal_spaces_workbook_tasks()
+ {
+ MockSessionProvider.SetupGet(p => p.InstanceType).Returns(TableauInstanceType.Cloud);
+
+ var response = CreateCloudResponse(ExtractRefreshContentType.Workbook);
+
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls().Skip(1));
+
+ SetupSuccessResponse(response);
+
+ var result = await CloudTasksApiClient.GetAllExtractRefreshTasksAsync(Cancel);
+
+ var actualExtractRefreshes = AssertSuccess(result);
+ var expectedExtractRefreshes = response.Items.ToList();
+
+ Assert.Equal(expectedExtractRefreshes.Count - 1, actualExtractRefreshes.Count);
+ }
}
#endregion
@@ -263,7 +288,7 @@ public async Task Creates_extract_refresh_for_workbook_successfully()
response.Schedule!.Frequency = cloudSchedule.Frequency;
SetupSuccessResponse(response);
- SetupExtractRefreshContentFinder(response.Item);
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Item);
// Act
var result = await CloudTasksApiClient.CreateExtractRefreshTaskAsync(
@@ -296,7 +321,7 @@ public async Task Creates_extract_refresh_for_datasource_successfully()
response.Schedule!.Frequency = cloudSchedule.Frequency;
SetupSuccessResponse(response);
- SetupExtractRefreshContentFinder(response.Item);
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Item);
// Act
var result = await CloudTasksApiClient.CreateExtractRefreshTaskAsync(
@@ -334,6 +359,34 @@ public async Task Fails_to_create_extract_refresh()
Assert.False(result.Success);
Assert.Null(result.Value);
}
+
+ [Fact]
+ public async Task Fails_content_reference_not_found()
+ {
+ // Arrange
+ MockSessionProvider.SetupGet(p => p.InstanceType).Returns(TableauInstanceType.Cloud);
+ var contentReference = AutoFixture.Create();
+ var cloudSchedule = AutoFixture.Create();
+ var createTaskOptions = new CreateExtractRefreshTaskOptions(
+ ExtractRefreshType.FullRefresh,
+ ExtractRefreshContentType.Workbook,
+ contentReference.Id,
+ cloudSchedule);
+
+ var response = AutoFixture.CreateResponse();
+ response.Item!.DataSource = null;
+ response.Item.Workbook!.Id = contentReference.Id;
+ response.Schedule!.Frequency = cloudSchedule.Frequency;
+
+ SetupSuccessResponse(response);
+
+ // Act
+ var result = await CloudTasksApiClient.CreateExtractRefreshTaskAsync(createTaskOptions, Cancel);
+
+ // Assert
+ result.AssertFailure();
+ Assert.IsType(result.Errors.Single());
+ }
}
#endregion
@@ -381,7 +434,7 @@ public async Task Gets_workbook_extract_refreshes()
MockSessionProvider.SetupGet(p => p.InstanceType).Returns(TableauInstanceType.Server);
var response = CreateServerResponse(ExtractRefreshContentType.Workbook);
- SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
SetupSuccessResponse(response);
@@ -401,7 +454,7 @@ public async Task Gets_datasource_extract_refreshes()
var response = CreateServerResponse(ExtractRefreshContentType.DataSource);
- SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls());
SetupSuccessResponse(response);
@@ -413,6 +466,25 @@ public async Task Gets_datasource_extract_refreshes()
Assert.Equal(expectedExtractRefreshes.Count, actualExtractRefreshes.Count);
Assert.DoesNotContain(actualExtractRefreshes, item => item.ContentType == ExtractRefreshContentType.Workbook);
}
+
+ [Fact]
+ public async Task Ignores_personal_spaces_workbook_tasks()
+ {
+ MockSessionProvider.SetupGet(p => p.InstanceType).Returns(TableauInstanceType.Server);
+
+ var response = CreateServerResponse(ExtractRefreshContentType.Workbook);
+
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls().Skip(1));
+
+ SetupSuccessResponse(response);
+
+ var result = await ServerTasksApiClient.GetAllExtractRefreshTasksAsync(Cancel);
+
+ var actualExtractRefreshes = AssertSuccess(result);
+ var expectedExtractRefreshes = response.Items.ToList();
+
+ Assert.Equal(expectedExtractRefreshes.Count - 1, actualExtractRefreshes.Count);
+ }
}
#endregion
diff --git a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Cloud/CloudExtractRefreshTaskTests.cs b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Cloud/CloudExtractRefreshTaskTests.cs
index 1186a5d..424b0e1 100644
--- a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Cloud/CloudExtractRefreshTaskTests.cs
+++ b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Cloud/CloudExtractRefreshTaskTests.cs
@@ -16,9 +16,10 @@
//
using System;
+using System.Linq;
+using System.Threading.Tasks;
using Tableau.Migration.Api.Rest.Models.Responses;
using Tableau.Migration.Api.Rest.Models.Responses.Cloud;
-using Tableau.Migration.Api.Rest.Models.Types;
using Tableau.Migration.Content.Schedules;
using Tableau.Migration.Content.Schedules.Cloud;
using Xunit;
@@ -27,7 +28,7 @@
namespace Tableau.Migration.Tests.Unit.Content.Schedules.Cloud
{
- public class CloudExtractRefreshTaskTests
+ public sealed class CloudExtractRefreshTaskTests
{
public abstract class CloudExtractRefreshTaskTest : ExtractRefreshTaskTestBase
{
@@ -55,7 +56,7 @@ internal CloudExtractRefreshTask CreateExtractRefreshTask(
}
}
- public class Ctor : CloudExtractRefreshTaskTest
+ public sealed class Ctor : CloudExtractRefreshTaskTest
{
[Theory, ExtractRefreshContentTypeData]
public void Initializes(ExtractRefreshContentType contentType)
@@ -73,5 +74,28 @@ public void Initializes(ExtractRefreshContentType contentType)
Assert.Same(schedule, task.Schedule);
}
}
+
+ public sealed class CreateManyAsync : CloudExtractRefreshTaskTest
+ {
+ [Theory, ExtractRefreshContentTypeData]
+ public async Task IgnoresPersonalSpaceTasksAsync(ExtractRefreshContentType contentType)
+ {
+ var response = new ExtractRefreshTasksResponse
+ {
+ Items = Enumerable.Range(1, 10)
+ .Select(i => new ExtractRefreshTasksResponse.TaskType
+ {
+ ExtractRefresh = CreateExtractRefreshResponse(GetRandomType(), contentType)
+ })
+ .ToArray()
+ };
+
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls().Skip(1));
+
+ var tasks = await CloudExtractRefreshTask.CreateManyAsync(response, MockFinderFactory.Object, Logger, Localizer, Cancel);
+
+ Assert.Equal(response.Items.Length - 1, tasks.Count);
+ }
+ }
}
}
diff --git a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTaskTestBase.cs b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTaskTestBase.cs
index e0916fc..36cd2ef 100644
--- a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTaskTestBase.cs
+++ b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTaskTestBase.cs
@@ -15,13 +15,42 @@
// limitations under the License.
//
+using Microsoft.Extensions.Logging;
using Moq;
+using Tableau.Migration.Content;
+using Tableau.Migration.Content.Schedules.Server;
using Tableau.Migration.Content.Search;
+using Tableau.Migration.Resources;
namespace Tableau.Migration.Tests.Unit.Content.Schedules
{
public abstract class ExtractRefreshTaskTestBase : ScheduleTestBase
{
- protected readonly Mock MockFinderFactory = new();
+ protected readonly Mock MockFinderFactory = new() { CallBase = true };
+
+ protected readonly Mock> MockDataSourceFinder = new();
+
+ protected readonly Mock> MockScheduleCache = new();
+
+ protected readonly Mock> MockScheduleFinder = new();
+
+ protected readonly Mock> MockWorkbookFinder = new();
+
+ protected ILogger Logger { get; }
+
+ protected ISharedResourcesLocalizer Localizer { get; }
+
+ protected ExtractRefreshTestCaches ExtractRefreshTestCaches { get; }
+
+ protected ExtractRefreshTaskTestBase()
+ {
+ Logger = Create();
+ Localizer = Create();
+
+ MockFinderFactory.Setup(x => x.ForContentType()).Returns(MockDataSourceFinder.Object);
+ MockFinderFactory.Setup(x => x.ForContentType()).Returns(MockWorkbookFinder.Object);
+
+ ExtractRefreshTestCaches = new(AutoFixture, MockDataSourceFinder, MockWorkbookFinder, MockScheduleFinder, MockScheduleCache);
+ }
}
}
diff --git a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTestCaches.cs b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTestCaches.cs
new file mode 100644
index 0000000..35c9236
--- /dev/null
+++ b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/ExtractRefreshTestCaches.cs
@@ -0,0 +1,109 @@
+//
+// Copyright (c) 2024, Salesforce, Inc.
+// SPDX-License-Identifier: Apache-2
+//
+// Licensed under the Apache License, Version 2.0 (the "License")
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AutoFixture;
+using Moq;
+using Tableau.Migration.Api.Rest.Models.Responses;
+using Tableau.Migration.Api.Rest.Models.Responses.Server;
+using Tableau.Migration.Content;
+using Tableau.Migration.Content.Schedules;
+using Tableau.Migration.Content.Schedules.Server;
+using Tableau.Migration.Content.Search;
+
+namespace Tableau.Migration.Tests.Unit.Content.Schedules
+{
+ public sealed class ExtractRefreshTestCaches
+ {
+ private readonly IFixture _fixture;
+
+ private readonly Mock> _mockServerScheduleCache;
+ private readonly Mock> _mockDataSourceFinder;
+ private readonly Mock> _mockServerScheduleFinder;
+ private readonly Mock> _mockWorkbookFinder;
+
+ public ExtractRefreshTestCaches(IFixture fixture,
+ Mock> mockDataSourceFinder, Mock> mockWorkbookFinder,
+ Mock> mockServerScheduleFinder, Mock> mockServerScheduleCache)
+ {
+ _fixture = fixture;
+ _mockDataSourceFinder = mockDataSourceFinder;
+ _mockServerScheduleCache = mockServerScheduleCache;
+ _mockServerScheduleFinder = mockServerScheduleFinder;
+ _mockWorkbookFinder = mockWorkbookFinder;
+
+ // Set default finder behavior to return null on missing items.
+ _mockDataSourceFinder.Setup(x => x.FindByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync((IContentReference?)null);
+ _mockWorkbookFinder.Setup(x => x.FindByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync((IContentReference?)null);
+ }
+
+ public void SetupExtractRefreshContentFinder(IExtractRefreshType extractRefresh)
+ {
+ var contentType = extractRefresh.GetContentType();
+ var contentId = extractRefresh.GetContentId();
+
+ var mockReference = _fixture.Create>();
+ mockReference.SetupGet(r => r.Id).Returns(contentId);
+
+ var contentReference = mockReference.Object;
+
+ switch (contentType)
+ {
+ case ExtractRefreshContentType.DataSource:
+ SetupContentFinder(_mockDataSourceFinder);
+ break;
+
+ case ExtractRefreshContentType.Workbook:
+ SetupContentFinder(_mockWorkbookFinder);
+ break;
+
+ default:
+ throw new NotSupportedException($"Content type {contentType} is not supported.");
+ }
+
+ if (extractRefresh is IServerExtractRefreshType serverExtractRefresh)
+ {
+ Guard.AgainstNull(serverExtractRefresh.Schedule, () => serverExtractRefresh.Schedule);
+
+ var scheduleId = serverExtractRefresh.Schedule.Id;
+
+ var mockSchedule = _fixture.Create>();
+ mockSchedule.SetupGet(s => s.Id).Returns(scheduleId);
+
+ var cachedSchedule = mockSchedule.Object;
+ var scheduleReference = cachedSchedule.ToStub();
+
+ _mockServerScheduleFinder.Setup(f => f.FindByIdAsync(scheduleId, It.IsAny())).ReturnsAsync(scheduleReference);
+
+ _mockServerScheduleCache.Setup(f => f.ForIdAsync(scheduleId, It.IsAny())).ReturnsAsync(cachedSchedule);
+ }
+
+ void SetupContentFinder(Mock> mockFinder)
+ where T : IContentReference
+ => mockFinder.Setup(f => f.FindByIdAsync(contentId, It.IsAny())).ReturnsAsync(contentReference);
+ }
+
+ public void SetupExtractRefreshContentFinder(IEnumerable extractRefreshes)
+ {
+ foreach (var extractRefresh in extractRefreshes)
+ SetupExtractRefreshContentFinder(extractRefresh);
+ }
+
+ }
+}
diff --git a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Server/ServerExtractRefreshTaskTests.cs b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Server/ServerExtractRefreshTaskTests.cs
index f9a65cd..bb1722a 100644
--- a/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Server/ServerExtractRefreshTaskTests.cs
+++ b/tests/Tableau.Migration.Tests/Unit/Content/Schedules/Server/ServerExtractRefreshTaskTests.cs
@@ -16,10 +16,11 @@
//
using System;
+using System.Linq;
+using System.Threading.Tasks;
using Moq;
using Tableau.Migration.Api.Rest.Models.Responses;
using Tableau.Migration.Api.Rest.Models.Responses.Server;
-using Tableau.Migration.Api.Rest.Models.Types;
using Tableau.Migration.Content.Schedules;
using Tableau.Migration.Content.Schedules.Server;
using Tableau.Migration.Content.Search;
@@ -29,12 +30,11 @@
namespace Tableau.Migration.Tests.Unit.Content.Schedules.Server
{
- public class ServerExtractRefreshTaskTests
+ public sealed class ServerExtractRefreshTaskTests
{
public abstract class ServerExtractRefreshTaskTest : ExtractRefreshTaskTestBase
{
protected readonly Mock MockContentCacheFactory = new();
- protected readonly Mock> MockScheduleCache = new();
public ServerExtractRefreshTaskTest()
{
@@ -67,7 +67,7 @@ internal ServerExtractRefreshTask CreateExtractRefreshTask(
}
}
- public class Ctor : ServerExtractRefreshTaskTest
+ public sealed class Ctor : ServerExtractRefreshTaskTest
{
[Theory, ExtractRefreshContentTypeData]
public void Initializes(ExtractRefreshContentType contentType)
@@ -85,5 +85,28 @@ public void Initializes(ExtractRefreshContentType contentType)
Assert.Same(schedule, task.Schedule);
}
}
+
+ public sealed class CreateManyAsync : ServerExtractRefreshTaskTest
+ {
+ [Theory, ExtractRefreshContentTypeData]
+ public async Task IgnoresPersonalSpaceTasksAsync(ExtractRefreshContentType contentType)
+ {
+ var response = new ExtractRefreshTasksResponse
+ {
+ Items = Enumerable.Range(1, 10)
+ .Select(i => new ExtractRefreshTasksResponse.TaskType
+ {
+ ExtractRefresh = CreateExtractRefreshResponse(GetRandomType(), contentType)
+ })
+ .ToArray()
+ };
+
+ ExtractRefreshTestCaches.SetupExtractRefreshContentFinder(response.Items.Select(i => i.ExtractRefresh).ExceptNulls().Skip(1));
+
+ var tasks = await ServerExtractRefreshTask.CreateManyAsync(response, MockFinderFactory.Object, MockContentCacheFactory.Object, Logger, Localizer, Cancel);
+
+ Assert.Equal(response.Items.Length - 1, tasks.Count);
+ }
+ }
}
}