Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Release 4.3.1
Browse files Browse the repository at this point in the history
drache42 committed Sep 12, 2024
1 parent ca234f6 commit 774da09
Showing 15 changed files with 343 additions and 189 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -177,6 +177,7 @@ launchSettings.json
UpgradeLog.htm
*.*.ini
*.*.json
src/Documentation/python

# Public repo ignores
.github/pull_request_template.md
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Version>4.3.0</Version>
<Version>4.3.1</Version>
<Authors>Salesforce, Inc.</Authors>
<Company>Salesforce, Inc.</Company>
<Copyright>Copyright (c) 2024, Salesforce, Inc. and its licensors</Copyright>
Original file line number Diff line number Diff line change
@@ -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<IContentReference> 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<IContentReference?> FindAsync<TResponse, TContent>(
this IContentReferenceFinderFactory finderFactory,
[NotNull] TResponse? response,
42 changes: 17 additions & 25 deletions src/Tableau.Migration/Api/TasksApiClient.cs
Original file line number Diff line number Diff line change
@@ -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(

/// <inheritdoc />
public IServerTasksApiClient ForServer()
=> ExecuteForInstanceType(
TableauInstanceType.Server,
_sessionProvider.InstanceType,
() => this);
=> ExecuteForInstanceType(TableauInstanceType.Server, _sessionProvider.InstanceType, () => this);

/// <inheritdoc />
public ICloudTasksApiClient ForCloud()
=> ExecuteForInstanceType(
TableauInstanceType.Cloud,
_sessionProvider.InstanceType,
() => this);
=> ExecuteForInstanceType(TableauInstanceType.Cloud, _sessionProvider.InstanceType, () => this);

#endregion

@@ -105,10 +100,7 @@ public async Task<IResult> DeleteExtractRefreshTaskAsync(
async Task<IResult<IImmutableList<ICloudExtractRefreshTask>>> ICloudTasksApiClient.GetAllExtractRefreshTasksAsync(
CancellationToken cancel)
=> await GetAllExtractRefreshTasksAsync<CloudResponses.ExtractRefreshTasksResponse, ICloudExtractRefreshTask, ICloudSchedule>(
(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<IResult<ICloudExtractRefreshTask>> ICloudTasksApiClient.CreateExtract
.ForPostRequest()
.WithXmlContent(new CreateExtractRefreshTaskRequest(options))
.SendAsync<CloudResponses.CreateExtractRefreshTaskResponse>(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<IResult<ICloudExtractRefreshTask>> PublishAsync(
/// <inheritdoc />
async Task<IResult<IImmutableList<IServerExtractRefreshTask>>> IServerTasksApiClient.GetAllExtractRefreshTasksAsync(CancellationToken cancel)
=> await GetAllExtractRefreshTasksAsync<ServerResponses.ExtractRefreshTasksResponse, IServerExtractRefreshTask, IServerSchedule>(
(r, c) => ServerExtractRefreshTask.CreateManyAsync(
r,
ContentFinderFactory,
_contentCacheFactory,
c),
(r, c) => ServerExtractRefreshTask.CreateManyAsync(r, ContentFinderFactory, _contentCacheFactory, Logger, SharedResourcesLocalizer, c),
cancel)
.ConfigureAwait(false);

Original file line number Diff line number Diff line change
@@ -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<IImmutableList<ICloudExtractRefreshTask>> 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<ICloudExtractRefreshTask> 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));
}
}
}
72 changes: 31 additions & 41 deletions src/Tableau.Migration/Content/Schedules/ExtractRefreshTaskBase.cs
Original file line number Diff line number Diff line change
@@ -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<TSchedule> :
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<TExtractRefreshTask> CreateAsync<TExtractRefreshType, TExtractRefreshTask>(
TExtractRefreshType? response,
IContentReferenceFinderFactory finderFactory,
Func<TExtractRefreshType, IContentReference, CancellationToken, Task<TExtractRefreshTask>> modelFactory,
CancellationToken cancel)
where TExtractRefreshType : class, IExtractRefreshType
where TExtractRefreshTask: IExtractRefreshTask<TSchedule>
{
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<IImmutableList<TExtractRefreshTask>> CreateManyAsync<TResponse,TExtractRefreshType, TExtractRefreshTask>(
TResponse? response,
protected static async Task<IImmutableList<TExtractRefreshTask>> CreateManyAsync<TResponse, TExtractRefreshType, TExtractRefreshTask>(
TResponse response,
Func<TResponse, IEnumerable<TExtractRefreshType?>> responseItemFactory,
Func<TExtractRefreshType, IContentReference, CancellationToken, Task<TExtractRefreshTask>> modelFactory,
IContentReferenceFinderFactory finderFactory,
ILogger logger, ISharedResourcesLocalizer localizer,
CancellationToken cancel)
where TResponse : ITableauServerResponse
where TExtractRefreshType : class, IExtractRefreshType
where TExtractRefreshTask: IExtractRefreshTask<TSchedule>
{
Guard.AgainstNull(response, nameof(response));

var tasks = ImmutableArray.CreateBuilder<TExtractRefreshTask>();

var items = responseItemFactory(response).ExceptNulls();
var items = responseItemFactory(response).ExceptNulls().ToImmutableArray();
var tasks = ImmutableArray.CreateBuilder<TExtractRefreshTask>(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();
Original file line number Diff line number Diff line change
@@ -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<IImmutableList<IServerExtractRefreshTask>> 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<IServerExtractRefreshTask> CreateAsync(
IServerExtractRefreshType? response,
IServerExtractRefreshType response,
IContentReference content,
IContentCacheFactory contentCacheFactory,
CancellationToken cancel)
{
Guard.AgainstNull(response, nameof(response));

var scheduleCache = contentCacheFactory.ForContentType<IServerSchedule>(true);

var schedule = await scheduleCache.ForIdAsync(response.Schedule.Id, cancel).ConfigureAwait(false);
2 changes: 2 additions & 0 deletions src/Tableau.Migration/Resources/SharedResourceKeys.cs
Original file line number Diff line number Diff line change
@@ -142,5 +142,7 @@ internal static class SharedResourceKeys
public const string UserWithCustomViewDefaultSkippedMissingReferenceWarning = "UserWithCustomViewDefaultSkippedMissingReferenceWarning";

public const string DuplicateContentTypeConfigurationMessage = "DuplicateContentTypeConfigurationMessage";

public const string UnknownExtractRefreshContentTypeWarning = "UnknownExtractRefreshContentTypeWarning";
}
}
3 changes: 3 additions & 0 deletions src/Tableau.Migration/Resources/SharedResources.resx
Original file line number Diff line number Diff line change
@@ -315,4 +315,7 @@ Owner with ID {OwnerID}: {owner}</value>
<data name="DuplicateContentTypeConfigurationMessage" xml:space="preserve">
<value>Duplicate content type configuration found for content type {0}.</value>
</data>
<data name="UnknownExtractRefreshContentTypeWarning" xml:space="preserve">
<value>The extract refresh task with ID {TaskId} references an unknown content type. The task will be ignored.</value>
</data>
</root>
59 changes: 0 additions & 59 deletions tests/Tableau.Migration.Tests/Unit/Api/ApiTestBase.cs
Original file line number Diff line number Diff line change
@@ -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<TService>()
where TService : class
=> ActivatorUtilities.CreateInstance<TService>(Dependencies);

protected void SetupExtractRefreshContentFinder(IExtractRefreshType extractRefresh)
{
var contentType = extractRefresh.GetContentType();
var contentId = extractRefresh.GetContentId();

var contentReference = Create<Mock<IContentReference>>(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<Mock<IServerSchedule>>(m =>
{
m.SetupGet(s => s.Id).Returns(scheduleId);
})
.Object;

var scheduleReference = cachedSchedule.ToStub();

MockScheduleFinder.Setup(f => f.FindByIdAsync(scheduleId, It.IsAny<CancellationToken>())).ReturnsAsync(scheduleReference);

MockScheduleCache.Setup(f => f.ForIdAsync(scheduleId, It.IsAny<CancellationToken>())).ReturnsAsync(cachedSchedule);
}

void SetupContentFinder<T>(Mock<IContentReferenceFinder<T>> mockFinder)
where T : IContentReference
=> mockFinder.Setup(f => f.FindByIdAsync(contentId, It.IsAny<CancellationToken>())).ReturnsAsync(contentReference);
}

protected void SetupExtractRefreshContentFinder(IEnumerable<IExtractRefreshType> extractRefreshes)
{
foreach (var extractRefresh in extractRefreshes)
SetupExtractRefreshContentFinder(extractRefresh);
}
}
}
86 changes: 79 additions & 7 deletions tests/Tableau.Migration.Tests/Unit/Api/TasksApiClientTests.cs
Original file line number Diff line number Diff line change
@@ -30,23 +30,29 @@
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<ITasksApiClient>
{
internal virtual TasksApiClient TasksApiClient => GetApiClient<TasksApiClient>();

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<TExtractRefreshTask> AssertSuccess<TExtractRefreshTask, TSchedule>(IResult<IImmutableList<TExtractRefreshTask>> 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<ICloudExtractRefreshTask, ICloudSchedule>(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<IContentReference>();
var cloudSchedule = AutoFixture.Create<ICloudSchedule>();
var createTaskOptions = new CreateExtractRefreshTaskOptions(
ExtractRefreshType.FullRefresh,
ExtractRefreshContentType.Workbook,
contentReference.Id,
cloudSchedule);

var response = AutoFixture.CreateResponse<CloudResponses.CreateExtractRefreshTaskResponse>();
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<ArgumentNullException>(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<IServerExtractRefreshTask, IServerSchedule>(result);
var expectedExtractRefreshes = response.Items.ToList();

Assert.Equal(expectedExtractRefreshes.Count - 1, actualExtractRefreshes.Count);
}
}

#endregion
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<IContentReferenceFinderFactory> MockFinderFactory = new();
protected readonly Mock<IContentReferenceFinderFactory> MockFinderFactory = new() { CallBase = true };

protected readonly Mock<IContentReferenceFinder<IDataSource>> MockDataSourceFinder = new();

protected readonly Mock<IContentCache<IServerSchedule>> MockScheduleCache = new();

protected readonly Mock<IContentReferenceFinder<IServerSchedule>> MockScheduleFinder = new();

protected readonly Mock<IContentReferenceFinder<IWorkbook>> MockWorkbookFinder = new();

protected ILogger Logger { get; }

protected ISharedResourcesLocalizer Localizer { get; }

protected ExtractRefreshTestCaches ExtractRefreshTestCaches { get; }

protected ExtractRefreshTaskTestBase()
{
Logger = Create<ILogger>();
Localizer = Create<ISharedResourcesLocalizer>();

MockFinderFactory.Setup(x => x.ForContentType<IDataSource>()).Returns(MockDataSourceFinder.Object);
MockFinderFactory.Setup(x => x.ForContentType<IWorkbook>()).Returns(MockWorkbookFinder.Object);

ExtractRefreshTestCaches = new(AutoFixture, MockDataSourceFinder, MockWorkbookFinder, MockScheduleFinder, MockScheduleCache);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<IContentCache<IServerSchedule>> _mockServerScheduleCache;
private readonly Mock<IContentReferenceFinder<IDataSource>> _mockDataSourceFinder;
private readonly Mock<IContentReferenceFinder<IServerSchedule>> _mockServerScheduleFinder;
private readonly Mock<IContentReferenceFinder<IWorkbook>> _mockWorkbookFinder;

public ExtractRefreshTestCaches(IFixture fixture,
Mock<IContentReferenceFinder<IDataSource>> mockDataSourceFinder, Mock<IContentReferenceFinder<IWorkbook>> mockWorkbookFinder,
Mock<IContentReferenceFinder<IServerSchedule>> mockServerScheduleFinder, Mock<IContentCache<IServerSchedule>> 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<Guid>(), It.IsAny<CancellationToken>())).ReturnsAsync((IContentReference?)null);
_mockWorkbookFinder.Setup(x => x.FindByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>())).ReturnsAsync((IContentReference?)null);
}

public void SetupExtractRefreshContentFinder(IExtractRefreshType extractRefresh)
{
var contentType = extractRefresh.GetContentType();
var contentId = extractRefresh.GetContentId();

var mockReference = _fixture.Create<Mock<IContentReference>>();
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<Mock<IServerSchedule>>();
mockSchedule.SetupGet(s => s.Id).Returns(scheduleId);

var cachedSchedule = mockSchedule.Object;
var scheduleReference = cachedSchedule.ToStub();

_mockServerScheduleFinder.Setup(f => f.FindByIdAsync(scheduleId, It.IsAny<CancellationToken>())).ReturnsAsync(scheduleReference);

_mockServerScheduleCache.Setup(f => f.ForIdAsync(scheduleId, It.IsAny<CancellationToken>())).ReturnsAsync(cachedSchedule);
}

void SetupContentFinder<T>(Mock<IContentReferenceFinder<T>> mockFinder)
where T : IContentReference
=> mockFinder.Setup(f => f.FindByIdAsync(contentId, It.IsAny<CancellationToken>())).ReturnsAsync(contentReference);
}

public void SetupExtractRefreshContentFinder(IEnumerable<IExtractRefreshType> extractRefreshes)
{
foreach (var extractRefresh in extractRefreshes)
SetupExtractRefreshContentFinder(extractRefresh);
}

}
}
Original file line number Diff line number Diff line change
@@ -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<IContentCacheFactory> MockContentCacheFactory = new();
protected readonly Mock<IContentCache<IServerSchedule>> 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);
}
}
}
}

0 comments on commit 774da09

Please sign in to comment.