Base solution for your next web application
Open Closed

Unit Test Data Filter - Null IPrincipalAccessor #8574


User avatar
0
feloff created

Followed guidelines in article Custom Filters

Having difficulty with unit testing as the IPrincipalAccessor is always null, and even if it were to be injected the claims will never be added. Using the claims therefore seems the wrong way to do this - how should we implement to also allow for unit tests?


4 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team

    hi

    You can create a service and inject IPrincipalAccessor in this service to get the claim, inject this service in CustomFilterSampleDbContext, and then mock this service in the unit test to isolate IPrincipalAccessor.

  • User Avatar
    0
    feloff created

    Thank you - found a hack and it seems to work. Have a new issue with the data filter following the article though.

    When I evaluate the generated SQL command of the IQuerayable prior to execution all seems fine. The filter and parameters seems to be captured (replaced the select columns with * for readability)

    SELECT `c*
    FROM `Comments` AS `c`
    INNER JOIN (
        SELECT `p*
        FROM `Programmes` AS `p`
        WHERE (@__ef_filter__p_1 OR NOT (`p`.`IsDeleted`)) AND ((@__ef_filter__p_2 AND (@__ef_filter__p_3 OR EXISTS (
            SELECT 1
            FROM `ProgrammeBrokers` AS `p0`
            WHERE ((@__ef_filter__p_6 OR NOT (`p0`.`IsDeleted`)) AND (`p`.`Id` = `p0`.`ProgrammeId`)) AND ((`p0`.`BrokerId` = @__ef_filter__CurrentTenantId_4) AND (`p0`.`Status` <> 2))))) AND (@__ef_filter__p_5 OR ((`p`.`ProtectionBuyerId` = @__ef_filter__CurrentTenantId_4) OR (`p`.`ProtectionBuyerId` IS NULL AND @__ef_filter__CurrentTenantId_4 IS NULL))))
    ) AS `t` ON `c`.`ProgrammeId` = `t`.`Id`
    LEFT JOIN (
        SELECT `p1*
        FROM `ProgrammeAccess` AS `p1`
        INNER JOIN (
            SELECT `a*
            FROM `Approvals` AS `a`
            WHERE @__ef_filter__p_8 OR NOT (`a`.`IsDeleted`)
        ) AS `t0` ON `p1`.`AccessStatusId` = `t0`.`Id`
        WHERE @__ef_filter__p_7 OR NOT (`p1`.`IsDeleted`)
    ) AS `t1` ON `t`.`Id` = `t1`.`ProgrammeId`
    LEFT JOIN (
        SELECT `p2`.`*
        FROM `ProgrammeBrokers` AS `p2`
        WHERE @__ef_filter__p_6 OR NOT (`p2`.`IsDeleted`)
    ) AS `t2` ON `t`.`Id` = `t2`.`ProgrammeId`
    WHERE (`c`.`Discriminator` = 'ProgrammeComment') AND (@__ef_filter__p_0 OR NOT (`c`.`IsDeleted`))
    ORDER BY `c`.`Id`, `t`.`Id`, `t1`.`Id`, `t1`.`Id0`, `t2`.`Id`
    

    The final query only includes the isDeleted filter though and everything else is dropped

    DbCommand (3ms) [Parameters=[@__ef_filter__p_0='False'], CommandType='Text', CommandTimeout='30']
          SELECT COUNT(*)
          FROM `Comments` AS `c`
          WHERE (`c`.`Discriminator` = 'ProgrammeComment') AND (@__ef_filter__p_0 OR NOT (`c`.`IsDeleted`))
    

    The documentation suggests that it should pass the values via the property and it should all flow through. My code includes

    In the context:

            protected virtual bool CurrentIsBroker => GetCurrentIsBroker();
            protected virtual bool CurrentIsBuyer => GetCurrentIsBuyer();
            protected virtual bool CurrentIsSeller => GetCurrentIsSeller();
            ...
    
            protected override bool ShouldFilterEntity&lt;TEntity&gt;(IMutableEntityType entityType)
            {
                if (typeof(IMustHaveProgramme).IsAssignableFrom(typeof(TEntity)))
                    return true;
                ...
    
                return base.ShouldFilterEntity&lt;TEntity&gt;(entityType);
            }
    
            protected override Expression&lt;Func&lt;TEntity, bool&gt;> CreateFilterExpression&lt;TEntity&gt;()
            {
                if (typeof(IMustHaveProgramme).IsAssignableFrom(typeof(TEntity)))
                    {
                        Expression&lt;Func&lt;TEntity, bool&gt;> progFilter = p => !CurrentIsBroker || !IsMustHaveProgrammeFilterEnabled
                        || ((IMustHaveProgramme)p).Programme.Brokers.Any(b => b.BrokerId == this.CurrentTenantId && b.Status != BrokerAppointmentStatus.BrokerWithdrawn);
    
                        Expression&lt;Func&lt;TEntity, bool&gt;> buyerExpr = p => !CurrentIsBuyer || !IsMustHaveProgrammeFilterEnabled
                        || (((IMustHaveProgramme)p).Programme.ProtectionBuyerId == this.CurrentTenantId);
                        progFilter = progFilter == null ? buyerExpr : CombineExpressions(progFilter, buyerExpr);
    
                        Expression&lt;Func&lt;TEntity, bool&gt;> sellerExpr = p => !CurrentIsSeller || !IsMustHaveProgrammeFilterEnabled
                        || ((IMustHaveProgramme)p).Programme.AccessRequests.Any(a => a.AccessStatus.Status == ApprovalStatus.Approved && a.TenantId == this.CurrentTenantId);
                        progFilter = progFilter == null ? sellerExpr : CombineExpressions(progFilter, sellerExpr);
    
                        expression = expression == null ? progFilter : CombineExpressions(expression, progFilter);
                    }
                    ...
            }
    

    and in core module - PreInitialize

    Configuration.UnitOfWork.RegisterFilter(ReinsuranceConst.IMustHaveProgramme, true);

    I can also see my filters are enabled and the property values are correctly set when debugging, but the final query doesn't filter.

    The 3 properties have the correct value:

    The filter is enabled and correctly refelcts in the property in the context:

    Is there anything else I need to do that is not included in the documentation?

    Many thanks

  • User Avatar
    0
    maliming created
    Support Team

    Can you create a project using abp's free template? I can check it for you.

    https://aspnetboilerplate.com/Templates

  • User Avatar
    0
    feloff created

    Spent the day copying logic into the template only to have a moment of clarity after it was all done:

    From docs

    Filters can only be defined for the root Entity Type of an inheritance hierarchy.